Синхронизация примитивов в .NET Framework: какая из них хорошая?
У меня проблема с пространством System.Threading
Microsoft.NET. В этом пространстве имен многие классы определены, чтобы помочь мне управлять потоками.
Ну, у меня проблема, но я не знаю, что использовать, MSDN расплывчата, и я до сих пор не понял, что делают классы. в частности, моя проблема связана с синхронизацией.
Проблема
У меня есть определенное количество потоков (рассмотрим N потоков).
В определенный момент поток должен останавливаться и ждать, пока хотя бы один из других потоков ничего не сделает.
Как только один из потоков N - 1 выполнил определенную задачу, этот поток уведомляет об этом и останавливается поток.
Итак, это всего лишь проблема синхронизации: поток должен ждать, чтобы его сигнализировали, что все.
Множество классов
В System.Threading
существует множество классов, предназначенных для решения проблем синхронизации. Существуют WaitHandle
(s), существуют AutoResetEvent
(s), существуют ManualResetEvent
(s) и т.д....
Какой я использую?
Вопрос
Мой вопрос: может ли кто-нибудь обобщить меня, какой класс я должен использовать, чтобы решить мою проблему? Не могли бы вы рассказать о самых важных различиях между этими классами или другими классами?
Дело в том, что я действительно не понял, на какой класс отвечает вопрос синхронизации: в чем разница, например, между WaitHandle
и AutoResetEvent
или ManualResetEvent
?
Как насчет блокировки?
Для обработки многих проблем с потоками .net предоставляет функции lock
и класс Monitor
. Эта пара хороша для моих нужд?
Thankyou
Ответы
Ответ 1
Книга Альбахари удивительна, вы должны действительно прочитать ее через некоторое время. Его выросли в последнее время!
Что вы хотите
Вы хотите EventWaitHandle
(EWH), они хороши, потому что ничего не обойти, они используются для сигнализации потоков ( либо в том же или в другом процессе), и, как следует из названия, их можно ожидать.
Как вы его используете
Вы откроете его в потоке, который выполняет ожидание, вы откроете его с заданным именем, о котором будет знать другой поток. Затем вы ждете этого дескриптора ожидания.
Сигнальная нить откроет существующий дескриптор ожидания с тем же именем (имя - строка) и вызовет set
.
Различия
AutoResetEvent
s и ManualResetEvent
s оба наследуются от EWH, и они на самом деле просто EWH, они просто действуют по-другому. Какой из них вы хотите, просто зависит от того, хотите ли вы, чтобы EWH выступал в качестве ворот или турникетов. Это вам особенно заботит, если вы используете дескриптор wait более одного раза, или вы ожидаете этого дескриптора ожидания более чем одним потоком. Я использовал wait handle для приличного количества (я полагаю), и я не думаю, что когда-либо использовал руководство.
Важно знать
-
Независимо от того, что вы делаете, не передавайте экземпляр дескриптора ожидания, они должны быть открыты отдельно своими потоками. Указанное вами имя будет гарантировать, что они являются "одним и тем же" дескриптором ожидания.
-
Если потоки находятся в разных процессах, вы должны префикс имени EWH с помощью @"Global\"
, иначе имена дескрипторов ожидания будут инкапсулированы в один и тот же процесс. Кроме того, если вы используете их в одном и том же процессе, не используйте глобальное пространство имен. Если вы не укажете префикс с обратным слэшем, он автоматически добавится, чтобы сохранить его закрытым, но вам не нужно знать этот префикс.
-
Имейте в виду, что EWH может быть разрешен, и если у вас возникнут проблемы с этим, рекомендую использовать EventWaitHandleRights.FullControl
, но вы можете просмотреть полный перечисление EventWaitHandleRights здесь.
-
Мне нравится называть EWH с Guid.NewGuid().ToString("N")
(Guid.NewGuid и Guid.ToString). Обычно я делаю это, когда поток сигнализации создается, так как вы можете легко передать ему информацию в это время. Поэтому в этом случае начальный поток создает строку и передает ее в сигнальный поток при его создании. Таким образом, оба потока знают о названии, без необходимости делать какие-либо причудливые перекрестные потоки переменных.
-
EWH реализует IDisposable
, чтобы обернуть его в using
блок
Условия гонки
EWH хороши, потому что, если по какой-либо причине поток сигнализации открывается и сигнализирует дескриптор ожидания до того, как поток ожидания даже создаст его, все будет работать, и ожидающий поток будет сигнализирован о том, как он попадает в ожидании.
В связи с этим поток, ожидающий этого, должен будет иметь некоторое захват ошибок, потому что вам нужно будет вызвать OpenExisting
. Если вы вызываете один из ctor
's, и EWH уже открыт, вы получите UnauthorizedAccessException
или WaitHandleCannotBeOpenedException
как описано здесь, в разделе Исключения. Вы все равно сможете открыть этот EWH и получить необходимую функциональность, вам просто нужно открыть его, а не создавать его.
Ответ 2
Разница между событием auto- reset и событием manual- reset заключается в том, что после того, как вы используете, автоматически удаляется (закрывается) событие auto-reset, поэтому через ворота проходит только один элемент. Я подозреваю, что AutoResetEvent
будет здесь хорошо. Лично я склонен использовать Monitor
больше, хотя - он имеет более низкие накладные расходы, но вам нужно быть немного осторожным; ваш первый поток должен обязательно иметь замок перед любым другим, т.е.
object lockObj = new object();
lock(lockObj) {
// start the workers, making lockObj available to them
Monitor.Wait(lockObj);
}
когда работники делают что-то вроде:
// lots of work
// now signal
lock(lockObj) Monitor.Pulse(lockObj);
// other work
Сохранение блокировки первоначально означает, что мы не пропускаем никаких сообщений, пока мы разворачиваем рабочих, так как любые рабочие, попадающие в lock(lockObj)
, будут заблокированы до тех пор, пока исходный поток не освободит блокировку в Monitor.Wait
. Первый поток Pulse
будет сигнализировать о продолжении нашего исходного потока.
Ответ 3
Существует отличная бесплатная электронная книга на эту тему (и проверьте часть 2)
Для чего использовать и когда на SO есть много тем, как этот: В чем разница между ManualResetEvent и AutoResetEvent в .NET?, процитировать Дэн Гольдштейн:
"Да, это похоже на разницу между платной дверью и дверью. ManualResetEvent - это дверь, которая должна быть закрыта (reset). AutoResetEvent - это tollbooth, позволяющая одному автомобилю проходить и автоматически закрываться до следующий может пройти".
Ответ 4
Вы можете использовать AutoResetEvent
или ManualResetEvent
. Единственное различие заключается в том, нужно ли вам позвонить Set()
самостоятельно или сделать это.
Ответ 5
Может случиться, или это имеет значение, если "один из N-1 потоков выполнил определенную задачу" происходит до того, как "поток должен остановиться и ждать" достигает своей "определенной точки"? Это может повлиять на выбор синхронизма.
Rgds,
Мартин