Массив объектов, обновление в одном потоке и чтение в другом - PullRequest
5 голосов
/ 18 декабря 2011

Упрощенный вопрос, чтобы дать более четкое представление о том, что я на самом деле спрашиваю

У меня есть два потока, назовите их A и B.Они разделяют один объект типа Foo, который имеет поле с именем Name и хранится в массиве типа Foo[] с индексом 0.Потоки всегда будут обращаться к индексу 0 в порядке, который уже гарантирован системой, поэтому нет условия гонки для потока B перед потоком A.

Порядок такой:

 // Thread A
 array[0].Name = "Jason";

 // Thread B
 string theName = array[0].Name

Как я уже говорил, этот порядок уже гарантирован, у нет способа для потока B прочитать значение перед потоком A

Я хочу убедиться в том, что две вещи:

  1. Оба потока получают последний объект с индексом 0.
  2. Поток B всегда получает последнее значение в.Name field

Маркировка Name как volatile не является опцией, поскольку реальные объекты намного сложнее и даже имеют собственные структуры, которые даже не могут иметь прикрепленный к ним атрибут volatile.

Теперь, удовлетворяя 1 легко (всегда получая последний объект), вы можете сделать .VolatileRead:

 // Thread A
 Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
 obj.Name = "Jason";

 // Thread B
 Foo obj = (Foo)Thread.VolatileRead(ref array[0]);
 string theName = obj.Name

Или вы можете вставить барьер памяти:

 // Thread A
 array[0].Name = "Jason";
 Thread.MemoryBarrier();

 // Thread B
 Thread.MemoryBarrier();
 string theName = array[0].Name

Итак, мой вопрос: достаточно ли этого для удовлетворения условия 2?Что я всегда получаю последнее значение из полей объекта, который я зачитываю?Если объект с индексом 0 не изменился, а Name изменился.Сделает ли VolatileRead или MemoryBarrier для индекса 0, что все поля в объекте с индексом 0 также получат свои последние значения?

Ответы [ 2 ]

2 голосов
/ 18 декабря 2011

Ни одно из этих решений, lock или volatile не решит вашу проблему.Потому что:

  1. volatile гарантирует, что переменные, измененные одним потоком, будут немедленно видны другим потокам, работающим с теми же данными (то есть они не кэшированы), а также что операции с этой переменной не переупорядочиваются.Не совсем то, что вам нужно.
  2. lock гарантирует, что запись / чтение не происходят одновременно, но не гарантирует их порядок .Это зависит от того, какой поток получил блокировку первым, что является недетерминированным.

Поэтому, если ваш поток:

Thread A read Name
Thread A modify Name
Thread B read Name

именно в этом порядке, вам нужно будет принудительно применитьэто событие (например, AutoresetEvent):

//Thread A
foo[0].Name = "John"; // write value
event.Set(); // signal B that write is completed

//Thread B
event.WaitOne(); // wait for signal
string name = foo[0].Name; // read value

Это гарантирует, что поток B не считывает переменную Name до тех пор, пока A не изменит его.

Edit : Хорошо, так что вы уверены, что вышеупомянутый поток соблюдается.Поскольку вы говорите, что не можете объявить поля volatile, я рекомендую использовать Thread.MemoryBarrier() для введения ограждений, обеспечивающих порядок:

//Thread A
foo[0].Name = "John"; // write value
Thread.MemoryBarrier();

//Thread B
Thread.MemoryBarrier();
string name = foo[0].Name; // read value

Для получения дополнительной информации проверьте этот документ: http://www.albahari.com/threading/part4.aspx

0 голосов
/ 18 декабря 2011

Замки решат вашу проблему, если я правильно понимаю.Это связано с тем, что блокировка генерирует неявные (полные) барьеры памяти вокруг себя.

Вы также можете явно использовать барьер памяти, используя Thread.MemoryBarrier.Подробнее здесь .Эффект барьера памяти может быть довольно трудно заметить на x86, но на более спокойной системе заказов, такой как PPC, он часто бывает существенным.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...