2008-12-08 7 views
8

В многопоточной программе, запущенной на машине с несколькими процессорами, мне нужно получить доступ к общему состоянию (_data в приведенном ниже примере кода) с использованием изменчивых операций чтения/записи для обеспечения правильности.Использование летучих (Thread.VolatileRead/Thread.VolatileWrite) в C#

Другими словами, можно ли кучи объектов кэшировать на процессоре?

Используя приведенный ниже пример и предполагая, что многопоточность будет обращаться к методам GetValue и Add, мне нужно, чтобы ThreadA мог добавлять данные (используя метод добавления) и ThreadB, чтобы иметь возможность сразу видеть/получать эти добавленные данные (используя метод GetValue). Так что мне нужно добавить волатильные чтения/записи в _data, чтобы это обеспечить? В основном я не хочу добавлять данные для кэширования на процессор ThreadA.

/Я не блокирую (соблюдая эксклюзивный доступ к потоку), так как код должен быть очень быстрым, и я не удаляю никаких данных из _data, поэтому мне не нужно блокировать _data.

Спасибо.

**** Обновление ****************************

Очевидно, вы, ребята, думаете, происходит блокировка свободного использования этот пример - плохая идея. Но какие побочные эффекты или исключения я могу увидеть здесь?

Может ли тип словаря генерировать исключение, если 1 поток выполняет итерацию значений для чтения, а другой поток выполняет итерацию значений для обновления? Или я просто испытаю «грязные чтения» (что было бы хорошо в моем случае)?

**** End Update ****************************

public sealed class Data 
{ 
    private volatile readonly Dictionary<string, double> _data = new Dictionary<string, double>(); 

    public double GetVaule(string key) 
    { 
     double value; 
     if (!_data.TryGetValue(key, out value)) 
     { 
      throw new ArgumentException(string.Format("Key {0} does not exist.", key)); 
     } 
     return value; 
    } 

    public void Add(string key, double value) 
    { 
     _data.Add(key, value); 
    } 

    public void Clear() 
    { 
     _data.Clear(); 
    } 
} 

Спасибо за ответы. Что касается замков, методы довольно часто вызываются многомиллионными потоками, поэтому моя проблема связана с оспариваемыми блокировками, а не с фактической операцией блокировки.

Итак, мой вопрос о кешировании cpu, может ли куча объектов (поле экземпляра _data) кэшироваться на процессоре? Нужен ли мне доступ к полю _data с помощью изменчивых операций чтения/записи?

/Кроме того, я застрял с .Net 2.0.

Благодарим за помощь.

ответ

2

Я не думаю, что volatile может быть заменой блокировки, если вы начинаете называть методы на ней. Вы гарантируете, что поток A и поток B видят одну и ту же копию словаря, но вы все равно можете получить доступ к словарю одновременно. Вы можете использовать многомодовые блокировки для увеличения параллелизма. См., Например, ReaderWriterLockSlim.

Представляет замок, который используется для управления доступом к ресурсам, позволяя нескольких потоков для чтения или монопольного доступа для записи.

16

MSDN docs для Dictionary<TKey, TValue> сказать, что это безопасно для нескольких читателей, но они не дают «один писатель, несколько читателей» гарантию того, что некоторые другие классы делают. Короче говоря, я бы этого не сделал.

Вы говорите, что избегаете блокировки, потому что вам нужен «ультрабыстрый» код. У вас есть блокировки, чтобы узнать, что такое накладные расходы? Непротиворечивые блокировки очень дешевы, и когда блокировка оспаривается, когда вы получаете дополнительную безопасность. Я бы, конечно, профилировал это, прежде чем решиться беспокоиться о проблемах параллелизма в решении без блокировки. ReaderWriterLockSlim может быть полезна, если у вас на самом деле есть несколько читателей, но похоже, что у вас есть один читатель и один писатель, по крайней мере на данный момент - простая блокировка в этом случае будет проще.

3

Проблема заключается в том, что ваш метод добавления:

public void Add(string key, double value) 
{ 
    _data.Add(key, value); 
} 

Может вызвать _data решить полностью реорганизовать данные, которые он держит - на тот момент запроса GetVaule мог не любым возможным способом.

Вам нужна блокировка или другая структура структуры данных/структуры данных.

6

Я думаю, что вы можете не понимать использование ключевого слова volatile (либо я, либо кто-то, пожалуйста, не стесняйтесь исправить меня). Ключевое слово volatile гарантирует, что получение и установка операций над значением самой переменной из нескольких потоков всегда будут иметь дело с одной и той же копией. Например, если у меня есть bool, который указывает состояние, то установка его в одном потоке сделает новое значение немедленно доступным для другого.

Тем не менее, вы никогда не изменяете значение своей переменной (в данном случае ссылку). Все, что вы делаете, это манипулировать областью памяти, на которую указывает эта точка. Объявляя его как volatile readonly (который, если мое понимание звучит, побеждает цель волатильности, никогда не позволяя ему быть установленным) не будет влиять на фактические данные, которые обрабатываются (внутреннее хранилище для Dictionary<>).

Все, что сказано, вам действительно нужно использовать замок в этом случае. Ваша опасность выходит за рамки перспективы «грязных чтений» (это означает, что то, что вы прочитали, было бы в какой-то момент действительным) на поистине неизвестную территорию. Как сказал Джон, вам действительно нужно доказательство того, что блокировка создает неприемлемую производительность, прежде чем вы попытаетесь пойти по пути бесконтактного кодирования. В противном случае это является воплощением преждевременной оптимизации.

0

Летучие компоненты не блокируются, это не имеет никакого отношения к синхронизации. Как правило, безопасно читать без фиксации данные только для чтения. Обратите внимание, что только потому, что вы ничего не удаляете из _data, вы, похоже, вызываете _data.Add(). Это НЕ только для чтения. Так что да, этот код будет взорваться в вашем лице во множестве интересных и трудно предсказать пути.

Используйте замки, это просто, безопаснее. Если вы не являетесь незащищенным гуру (нет!), А профилирование показывает узкое место, связанное с соперничеством за блокировку, и вы не можете решить проблемы конкуренции путем разбиения на разделы или переключения на прядильные блоки. ТОГДА И ТОЛЬКО ТОГДА вы можете исследуйте решение, чтобы получить незафиксированные чтения, которые будут включать в себя написание собственного словаря с нуля и МОГУТ быть быстрее, чем решение для блокировки.

Вы начинаете видеть, как далеко вы находитесь от своего мышления здесь? Просто используйте проклятый замок!

1

Ключевое слово volatile не связано с блокировкой, оно используется для указания того, что значение указанного поля может быть изменено или прочитано другим потоком или другой вещью, которая может работать одновременно с вашим кодом. Это важно для компилятора, потому что многие процессы оптимизации включают кэширование значения переменной и переупорядочение инструкций.Ключевое слово volatile подскажет компилятору «осторожно» при оптимизации тех инструкций, которые ссылаются на переменную volatile.

Для многопоточного использования словаря существует множество способов сделать это. Самый простой способ - использовать ключевое слово lock, которое имеет достаточную производительность. Если вам нужна более высокая производительность, вам может понадобиться реализовать свой собственный словарь для вашей конкретной задачи.