Использует ли это использование Monitor.Wait / Pulse в состоянии гонки? - PullRequest
2 голосов
/ 13 апреля 2010

У меня есть простой сценарий производителя / потребителя, где когда-либо производится или потребляется только один предмет. Кроме того, производитель ожидает завершения рабочего потока, прежде чем продолжить. Я понимаю, что это устраняет весь смысл многопоточности, но, пожалуйста, просто предположите, что это действительно так ((

)

Этот код не компилируется, но я надеюсь, что вы поняли:

// m_data is initially null

// This could be called by any number of producer threads simultaneously
void SetData(object foo)
{
    lock(x)                      // Line A
    {
        assert(m_data == null);
        m_data = foo;
        Monitor.Pulse(x)         // Line B
        while(m_data != null)
            Monitor.Wait(x)      // Line C
    }
}

// This is only ever called by a single worker thread
void UseData()
{
    lock(x)                      // Line D
    {
        while(m_data == null)
            Monitor.Wait(x)      // Line E
        // here, do something with m_data
        m_data = null;
        Monitor.Pulse(x)         // Line F
    }
}

Вот ситуация, в которой я не уверен:

Предположим, что многие потоки вызывают SetData () с разными входами. Только один из них попадет внутрь замка, а остальные будут заблокированы на линии А. Предположим, что тот, кто попал внутрь замка, устанавливает m_data и пробивается к линии C.

Вопрос: Может ли Wait () в строке C разрешить другому потоку в строке A получить блокировку и перезаписать m_data до того, как рабочий поток даже доберется до него?

Предположим, что этого не происходит, и рабочий поток обрабатывает исходные m_data и, в конце концов, попадает в Строку F, что произойдет, когда этот Pulse () отключится?

Сможет ли блокировка получить только поток, ожидающий на линии C? Или он будет конкурировать со всеми остальными потоками, ожидающими и в строке A?

По сути, я хочу знать, взаимодействуют ли Pulse () / Wait () друг с другом специально «под колпаком» или находятся ли они на одном уровне с lock ().

Решение этих проблем, если они существуют, конечно, очевидно - просто окружите SetData () другой блокировкой - скажем, lock (y). Мне просто любопытно, если это вообще проблема для начала.

1 Ответ

9 голосов
/ 13 апреля 2010

Нет никакой гарантии, что потребитель будет поставлен в очередь в очереди ожидания или готовности перед другим производителем.
Мониторы в стиле C # и Java описаны в Википедии, в разделе "Мониторы неявного состояния" .
Хороший обзор того, что происходит в Monitor (взято с этого отличного сайта): альтернативный текст http://www.albahari.com/threading/waitpulse.png

"Может ли функция Wait () в строке C разрешитьдругой поток в строке A для получения блокировки и перезаписи m_data еще до того, как рабочий поток до него доберется? "

Предположим, что SetData() вызывается двумя потоками производителя, P1 & P2 .
Запускается также потребительская нить, C1 .
P1 , P2 и C1 все входят в очередь готовности.
P1 сначала получает блокировку.
Очередь ожидания пуста, Pulse() на line B не действует.
P1 ожидает line C, поэтому он помещается в очередь ожидания.
Следующий поток в очереди готовности получает блокировку.
Это может быть равно P2 или C1 - в первом случае утверждение не выполняется.
У вас есть состояние гонки.

"Предположим, что этого не произойдет, и рабочий поток обрабатывает исходные данные m_data и, в конце концов, попадает в Строку F, что происходит при отключении Pulse ()?"

Он переместит официанта из очереди ожидания в очередь готовности.
Блокировка удерживается потоком, который выдает Pulse().
Уведомленный поток получит шанс получить для получения блокировки после того, как пульсирующий поток снимает блокировку (в очереди готовности уже могут быть другие).
От MSDN, Monitor.Pulse () :
"Поток, которому в настоящее время принадлежит блокировка для указанного объекта, вызывает этот метод, чтобы сигнализировать следующему потоку в строке о блокировке. После получения импульса ожидающий поток перемещается в очередь готовности. Когда поток, вызвавший Pulse, снимает блокировку,следующий поток в очереди готовности (который не обязательно является потоком, который был импульсным) получает блокировку. "

" Будет ли только поток, ожидающий в строке C, быть ablе получить замок?Или же он будет конкурировать со всеми другими потоками, ожидающими также в строке A? "

Все потоки, находящиеся в очереди готовности," соревнуются "за следующую блокировку.
Это ненезависимо от того, попали ли они прямо или из очереди ожидания с помощью Pulse().

«Очереди» могут быть реализованы другими способами. (Не структура данных queue как таковой).
Таким образом, реализация Monitor может не гарантировать справедливости, но может иметь более высокую общую пропускную способность / производительность.

...