.NET: Как убедиться, что поток 1 может видеть, что поток 2 записал в поле? - PullRequest
4 голосов
/ 18 августа 2010

Среда: .NET 3.5 SP1.

У меня есть два потока: поток пользовательского интерфейса и фоновый рабочий поток. Фоновый рабочий поток периодически обновляет некоторые поля в общем объекте, и поток пользовательского интерфейса проверяет их. Ничего впечатляющего - только прогресс, возвращаемые значения и брошенные исключения. Также рабочий поток вызывает некоторые события в потоке пользовательского интерфейса (через Control.BeginInvoke), когда он изменяет эти поля.

Рабочий поток ТОЛЬКО ЗАПИСЫВАЕТ эти поля, а поток пользовательского интерфейса ТОЛЬКО СЧИТАЕТ их. Они не используются для любого другого общения. Ради производительности я бы хотел избежать блокировки общего объекта или отдельных свойств. В общем объекте никогда не будет недопустимого состояния.

Однако меня беспокоят такие вещи, как кэши процессора и оптимизация компилятора. Как можно избежать ситуации, когда обновленное значение не отображается в обработчике событий в потоке пользовательского интерфейса? Достаточно ли будет добавить volatile во все поля?

Ответы [ 3 ]

1 голос
/ 18 августа 2010

Ты в порядке, не нужно беспокоиться.Барьер памяти необходим для сброса любых ожидающих записей в память.Есть неявный с любым оператором блокировки.Control.Begin / Invoke () должен принять блокировку для защиты списка ожидающих делегатов, поэтому этого достаточно.

Изменчивое требование является более сложным, в основном потому, что его точная семантика так плохо документирована.На оборудовании x86 / x64 это просто предотвращает кеширование JIT-компилятором значения переменной в регистре процессора.Это не проблема в вашем случае, потому что цель делегата указывает на метод.Переменные не кэшируются между методами, если методы не встроены.Ваша цель делегата не может быть встроена.

0 голосов
/ 18 августа 2010

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

object shared;

void WorkerThread()
{
  MakeChangesToSharedObject(shared);
  Thread.MemoryBarrier(); // commit the changes to main memory
}

void UIThread()
{
  Thread.MemoryBarrier(); // ensure updates are read from main memory
  UseSharedObject(shared);
}

Возможно, разработка кода таким образом, чтобы общий объект был неизменным, была бы лучше.Затем все, что вам нужно сделать, это заменить ссылку на общий объект в одну простую и быструю атомарную операцию.

volatile object shared;

void WorkerThread()
{
  // The following operation is safe because the variable is volatile.
  shared = GetNewSharedObject();
}

void UIThread()
{
  // The following operation is safe because the variable is volatile.
  object value = shared.SomeProperty;
}
0 голосов
/ 18 августа 2010

Гораздо лучше использовать установленные правила многопоточности. Коллекции производителей / потребителей доступны в .NET 4.0;они также доступны для .NET 3.5, если вы включите ссылки на библиотеку Rx .

Код без блокировки почти невозможно получить правильно.Если вы не хотите использовать очередь производителя / потребителя, используйте блокировку.

Если вы настаиваете на том, чтобы идти по пути боли, тогда да, volatile разрешит любому потоку, читающему эту переменную,получить последнее записанное значение.

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