Примитивы синхронизации в .NET Framework: какой из них лучше? - PullRequest
20 голосов
/ 16 июня 2011

У меня проблема с пространством имен Microsoft .NET System.Threading.В этом пространстве имен определено много классов, чтобы помочь мне управлять потоками.Ну, у меня есть проблема, но я не знаю, что использовать, MSDN расплывчато, и я до сих пор не понимаю, что и как делают классы.В частности, моя проблема касается синхронизации.

Проблема

У меня есть определенное количество потоков (рассмотрим N потоков).В определенный момент поток должен остановиться и ждать, пока хотя бы один из потоков что-то сделает.Как только один из N - 1 потоков выполнил определенную задачу, этот поток уведомляет об этом, и остановленный поток сможет продолжить.

Так что это всего лишь проблема синхронизации: поток должен ждать, пока не получит сигнал,all.

Множество классов

В System.Threading предусмотрено много классов для решения проблем синхронизации.Есть WaitHandle (s), есть AutoResetEvent (s), есть ManualResetEvent (s) и так далее ...

Какой из них мне использовать?

Вопрос

У меня такой вопрос: кто-нибудь может подсказать мне, какой класс мне следует использовать для решения моей проблемы?Не могли бы вы рассказать о наиболее важных различиях между этими или другими классами?

Дело в том, что я не совсем понял, за что отвечает класс в вопросе синхронизации: в чем, например, разницамежду WaitHandle и AutoResetEvent или ManualResetEvent?

А как насчет блокировки?

Для решения многих проблем с потоками .net предоставляет функции lock и Monitor учебный класс.Эта пара подходит для моих нужд?

Спасибо

Ответы [ 5 ]

18 голосов
/ 16 июня 2011

Книга Албахари удивительна, вы должны действительно прочитать ее некоторое время.В последнее время его очень много!

То, что вы хотите

Вы хотите EventWaitHandle (EWH), они хороши, потому что нечего обойти, они используютсядля сигнальных потоков (либо в том же, либо в другом процессе) и, как следует из названия, их можно ожидать.

Как вы используете это

Вы бы открыли один в потоке, которыйвыполняет ожидание, вы открываете его с заданным именем, о котором другой поток узнает.Затем вы ждете этот дескриптор ожидания.

Поток сигнализации откроет существующий дескриптор ожидания с тем же именем (имя является строкой) и вызовет set для него.

Различия

AutoResetEvent с и ManualResetEvent с оба наследуются от EWH и на самом деле они просто EWH, они просто действуют по-разному.Какой из них вам нужен, зависит только от того, хотите ли вы, чтобы EWH выступал в качестве ворот или турникета. Вас волнует это только в том случае, если вы используете дескриптор ожидания более одного раза или вы ожидаете в этом дескрипторе ожидания более одного потока. Я использовал ожидание, обрабатывает приличное количество (полагаю), и яне думайте, что я когда-либо использовал Руководство.

Важно знать

  • Что бы вы ни делали, не передавайте экземпляр дескриптора ожидания, они предназначеныбыть открытыми отдельно своими потоками.Указанное вами имя будет гарантировать, что они будут «одинаковыми» дескрипторами ожидания.

  • Если потоки находятся в разных процессах, то вам нужно будет добавить префикс имени EWH к @"Global\", иначе имена дескрипторов ожидания будут заключены в один и тот же процесс.В качестве альтернативы, если вы используете их все в одном и том же процессе, не используйте глобальное пространство имен.Когда вы не указываете префикс с обратной косой чертой, он автоматически добавляется, чтобы сохранить его в тайне, но вам не нужно знать этот префикс.

  • Имейте в виду, что EWH можетполучить разрешение, и если у вас возникнут проблемы с этим, я рекомендую вам использовать EventWaitHandleRights.FullControl, но вы можете просмотреть полное перечисление EventWaitHandleRights здесь .

  • Мне нравитсяназвать мои EWH с Guid.NewGuid().ToString("N") ( Guid.NewGuid & Guid.ToString ).Обычно я делаю это при создании потока сигнализации, поскольку вы можете легко передать ему информацию в это время.Таким образом, в этом случае начальный поток создает строку и передает ее сигнальному потоку при его создании.Таким образом, оба потока знают имя без необходимости какой-либо сложной передачи переменных между потоками.

  • EWH реализует IDisposable, поэтому оберните его вusing блок

Условия гонки

EWH хороши, потому что если по какой-либо причине поток сигнализации открывается и сигнализирует дескриптор ожидания перед ожиданиемпоток даже создает его, все будет работать, и ожидающий поток будет оповещен, как только он достигнет ожидания.

Из-за этого, тем не менее, поток, ожидающий его, должен будет иметь некоторую ловушку ошибок, потому что вам нужно будет вызвать OpenExisting.Если вы позвоните по одному из ctor и EWH уже открыт, вы получите UnauthorizedAccessException или WaitHandleCannotBeOpenedException, как описано здесь, в разделе Исключения ,Вы по-прежнему сможете открывать этот EWH и получать необходимую вам функциональность. Возможно, вам просто придется открыть его, а не создавать.

9 голосов
/ 16 июня 2011

Разница между событием автоматического сброса и событием ручного сброса заключается в том, что событие автоматического сброса очищается (закрывается) после одного использования, поэтому только один элемент проходит через ворота.Я подозреваю, и 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 будет сигнализировать о продолжении нашего исходного потока.

5 голосов
/ 16 июня 2011

Существует отличная бесплатная электронная книга на эту тему (и отметьте part 2 )

Для чего использовать и когда есть много тем оэто на SO, как этот: В чем разница между ManualResetEvent и AutoResetEvent в .NET? , чтобы процитировать Дэн Голдштейн:

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

1 голос
/ 16 июня 2011

Вы можете использовать AutoResetEvent или ManualResetEvent. Разница лишь в том, нужно ли вам звонить Set() самостоятельно или сделать это.

0 голосов
/ 16 июня 2011

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

Rgds, Martin

...