Monitor.Wait() и Monitor.Pulse() должны быть более производительными, чем использование производных классов EventWaitHandle, таких как ManualResetEvent для этого источника.
С точки зрения производительности вызов Pulse занимает около сотни наносекунд на настольном компьютере эпохи 2010 года — примерно треть времени, которое требуется для вызова Set по дескриптору ожидания.
Этот прирост производительности наверняка связан с тем, как помогает CLR — внутренние структуры данных, блоки синхронизации и т. д. Попытка понять исходный код, который в основном реализован на C/C++, для меня, как для разработчика C#, трудна, и мне, как разработчику C#, очень сложно понять, как это работает. удалось получить более глубокое погружение из этого ответа, который, к сожалению, немного не дает полного объяснения:
Отсюда мы добираемся до Object::Wait (строка 531), затем переходим к SyncBlock::Wait (строка 3442). На данный момент у нас есть большая часть мяса. реализация, и это немаловажно.
Учитывая все вышесказанное и возвращаясь к тому, что вы спросили, проще реализации, я бы поостерегся упрощать Monitor.Wait(). Eсть много чего происходит под капотом, и было бы очень легко ошибиться и иметь потенциальные ошибки в альтернативной реализации.
В коде SyncBlock::Wait можно найти:
CLREvent* hEvent;
...
// Before we enqueue it (and, thus, before it can be dequeued), reset the event
// that will awaken us.
hEvent->Reset();
Из этого ответа Ганса Пассанта на связанный с этим вопрос о том, переходит ли Monitor.Enter() в режим ядра:
Необходимо получить планировщик потоков операционной системы. задействовано, поэтому используется CLREvent. При необходимости создается динамически. и вызывается его метод WaitOne(). Что будет включать ядро переход.
Итак, мои вопросы
Войдите в режим ядра Monitor.Wait() и Monitor.Pulse() через вызовы методов CLREvent в их реализации. (Возможно ли вообще, чтобы они работали без участия ОС?)
Если да, то что делает их более производительными, чем использование тонких оболочек дескрипторов ОС AutoResetEvent и ManualResetEvent?
@Charlieface, так это все еще выглядит логикой MonitorEnter?
Что ты имеешь в виду?
@Charlieface, ты мог бы опубликовать свой предыдущий комментарий в качестве ответа. Я бы поддержал ваш ответ, но не буду голосовать за ваш комментарий, потому что комментарии имеют цель улучшить вопрос, а не ответить на него.
@TheodorZoulias Не знаю, это всего лишь TLDR довольно длинной статьи. Ответы, содержащие только ссылки, также не приветствуются. Вы можете написать ответ самостоятельно.
@Charlieface, возможно, я запутанно сформулировал вопрос. Статья, на которую вы ссылаетесь, описывает Monitor.Enter. Я знаю, как это работает, и это довольно очевидно. Чего я не знаю, так это того, как Monitor.Wait и Monitor.Pulse работают в тандеме с классом CLREvent(base).
@TheodorZoulias вопрос в том, может ли среда выполнения реализовать Monitor.Wait и Monitor.Pulse, не полагаясь на события ядра - т.е. собственное отслеживание того, какие потоки блокируются, пробуждение потоков вручную (имеет возможность GC) и т. д.
tinmanjk Я не изучал Wait/Pulse на уровне реализации. Я знаю только, как его использовать.
@TheodorZoulias Кажется, я нашел небольшое объяснение Джо Даффи в сообщении в блоге joeduffyblog.com/2007/06/24/clr-monitors-and-sync-blocks «Переменные условия реализованы немного иначе», чем в Monitor .Входить.





Джо Даффи в своем сообщении в блоге 2007 года дает обзорное объяснение того, как реализуются переменные состояния, то есть Monitor.Wait/Monitor.Pulse:
Условные переменные реализованы несколько иначе. Каждая среда CLR Объект потока имеет один посвященный ему объект события. Первый когда поток вызывает
Waitдля условной переменной, событие лениво выделено. И тогда поток просто помещает свой собственный локальный поток. событие в список событий, связанных с монитором. Регистрация событие также требует инфляции для блока синхронизации, если оно не было выполнено. уже произошло, потому что очевидно, что список событий не может быть сохранен в заголовок объекта. Когда случаетсяPulse, пульсирующая нить просто сигнализирует о первом событии в списке... При появленииPulseAllпульсирующая нить обходит весь список и сигнализирует каждому.
Похоже, что это все еще так, если посмотреть на исходный код для SyncBlock::Pulse и SyncBlock::PulseAll.
void SyncBlock::Pulse()
{
WaitEventLink *pWaitEventLink;
if ((pWaitEventLink = ThreadQueue::DequeueThread(this)) != NULL)
pWaitEventLink->m_EventWait->Set();
}
void SyncBlock::PulseAll()
{
WaitEventLink *pWaitEventLink;
while ((pWaitEventLink = ThreadQueue::DequeueThread(this)) != NULL)
pWaitEventLink->m_EventWait->Set();
}
Теперь вопрос в том, является ли этот объект события объектом события ядра.
Из исходного кода из SyncBlock::Wait
// First time this thread is going to wait for this SyncBlock.
CLREvent* hEvent;
if (pCurThread->m_WaitEventLink.m_Next == NULL) {
hEvent = &(pCurThread->m_EventWait);
}
else {
hEvent = GetEventFromEventStore();
}
waitEventLink.m_WaitSB = this;
waitEventLink.m_EventWait = hEvent;
waitEventLink.m_Thread = pCurThread;
waitEventLink.m_Next = NULL;
waitEventLink.m_LinkSB.m_pNext = NULL;
waitEventLink.m_RefCount = 1;
pWaitEventLink = &waitEventLink;
walk->m_Next = pWaitEventLink;
// Before we enqueue it (and, thus, before it can be dequeued), reset the event
// that will awaken us.
hEvent->Reset();
// This thread is now waiting on this sync block
ThreadQueue::EnqueueThread(pWaitEventLink, this);
isEnqueued = TRUE;
CLREvent наследуется от CLREventBase. Глядя на его методы, кажется, что это класс, который должен помочь в хостинговых возможностях CLR -
// Create an Event that is host aware
void CreateAutoEvent(BOOL bInitialState);
void CreateManualEvent(BOOL bInitialState);
// Create an Event that is not host aware
void CreateOSAutoEvent (BOOL bInitialState);
void CreateOSManualEvent (BOOL bInitialState);
Интерфейс IHostManager имеет очень похожий, если не идентичный API и его назначение:
Предоставляет методы, которые позволяют среде CLR создавать примитивы синхронизации, вызывая хост вместо использования функции синхронизации Win32.
Из официального clr-code-guidelines:
У нас есть следующие утвержденные механизмы синхронизации в CLR:
...
События. Хост может предоставлять дескрипторы событий, которые заменяют события Win32.
Итак, похоже, что по умолчанию создается событие CLREvent — это событие Win32.
Это было подтверждено автором этого поста в блоге , немного поработав с WinDbg и пытаясь определить, какой объект события используется Monitor.Enter - AutoResetEvent.
Судя по тому, что я смог покопаться в стеке вызовов Monitor.Wait, это событие изначально задано ManualResetEvent - например здесь
handle = new CLREvent;
_ASSERTE (handle != NULL);
handle->CreateManualEvent(TRUE);
return handle;
и здесь
bool CreateManualEvent(bool initialState)
{
m_hEvent = CreateEvent(nullptr, true, initialState, nullptr);
return IsValid();
}
что соответствует его использованию в Syncblock::Wait сверху
// Before we enqueue it (and, thus, before it can be dequeued), reset the event
// that will awaken us.
hEvent->Reset();
В заключение, по умолчанию (без переопределения хоста) вызовы Monitor.Wait и Monitor.Pulse(All) переходят в режим ядра, поскольку они полагаются на события ОС.