Я сделал поддержку классов и CLR для потоков в DotGNU, и у меня есть несколько мыслей ...
Если вам не требуются межпроцессные блокировки, вам всегда следует избегать использования Mutex и семафоров. Эти классы в .NET являются обертками для Win32 Mutex и Semaphores и имеют довольно большой вес (для них требуется переключение контекста в ядро, что является дорогостоящим - особенно если ваша блокировка не конфликтует).
Как уже упоминалось, оператор C # lock - это волшебство компилятора для Monitor.Enter и Monitor.Exit (существует в try / finally).
Мониторы имеют простой, но мощный механизм сигнала / ожидания, которого нет в мьютексах с помощью методов Monitor.Pulse / Monitor.Wait. Эквивалентом Win32 будут объекты событий через CreateEvent, которые на самом деле также существуют в .NET как WaitHandles. Модель Pulse / Wait аналогична Unix-системам pthread_signal и pthread_wait, но работает быстрее, поскольку в неконтролируемом случае они могут быть полностью операциями пользовательского режима.
Monitor.Pulse / Wait прост в использовании. В одном потоке мы блокируем объект, проверяем флаг / состояние / свойство и, если это не то, что мы ожидаем, вызываем Monitor.Wait, который снимет блокировку и будет ждать отправки импульса. Когда ожидание возвращается, мы возвращаемся назад и снова проверяем флаг / состояние / свойство. В другом потоке мы блокируем объект всякий раз, когда меняем флаг / состояние / свойство, а затем вызываем PulseAll для пробуждения любых прослушивающих потоков.
Часто мы хотим, чтобы наши классы были потокобезопасными, поэтому мы ставим блокировки в нашем коде. Однако часто бывает так, что наш класс будет когда-либо использоваться только одним потоком. Это означает, что блокировки неоправданно замедляют наш код ... именно здесь умная оптимизация в CLR может помочь повысить производительность.
Я не уверен насчет реализации Microsoft блокировок, но в DotGNU и Mono флаг состояния блокировки хранится в заголовке каждого объекта. Каждый объект в .NET (и Java) может стать блокировкой, поэтому каждый объект должен поддерживать это в своем заголовке. В реализации DotGNU есть флаг, который позволяет вам использовать глобальную хеш-таблицу для каждого объекта, который используется в качестве блокировки - это имеет преимущество, заключающееся в устранении 4-байтовых издержек для каждого объекта. Это не очень хорошо для памяти (особенно для встраиваемых систем, которые не имеют многопоточности), но сильно ухудшают производительность.
И Mono, и DotGNU эффективно используют мьютексы для выполнения блокировки / ожидания, но используют операции спин-блокировки в стиле сравнение и обмен , чтобы исключить необходимость фактически выполнять жесткие блокировки, если в этом нет особой необходимости:
Вы можете увидеть пример того, как мониторы могут быть реализованы здесь:
http://cvs.savannah.gnu.org/viewvc/dotgnu-pnet/pnet/engine/lib_monitor.c?revision=1.7&view=markup