Шаблон C# Producer-Consumer с Monitor.Wait и Monitor.Pulse
Рассмотрим следующую реализацию блокировки потоков производителя и потребителя:
static void Main(string[] args)
{
var syncRoot = new object();
var products = new List<int>();
Action producer = () => {
lock (syncRoot)
{
var counter = 0;
while (true)
{
products.Add(counter++);
Monitor.Pulse(syncRoot);
Monitor.Wait(syncRoot);
}
}};
Action consumer = () => {
lock (syncRoot)
while (true)
{
Monitor.Pulse(syncRoot);
products.ForEach(Console.WriteLine);
products.Clear();
Thread.Sleep(500);
Monitor.Wait(syncRoot);
}};
Task.Factory.StartNew(producer);
Task.Factory.StartNew(consumer);
Console.ReadLine();
}
Предполагая, что при создании потока входит Monitor.Wait
он ждет двух вещей:
- для пульсации от потребительской нити, и
- для повторной блокировки
В приведенном выше коде я делаю мою работу между Pulse
а также Wait
звонки.
Так что, если я напишу свою цепочку потребления следующим образом (Pulse непосредственно перед ожиданием):
Action consumer = () =>
{
lock (syncRoot)
while (true)
{
products.ForEach(Console.WriteLine);
products.Clear();
Thread.Sleep(500);
Monitor.Pulse(syncRoot);
Monitor.Wait(syncRoot);
}
};
Я не заметил каких-либо изменений в поведении. Есть ли какие-либо рекомендации к этому? Должны ли мы вообще Pulse
непосредственно перед тем, как мы Wait
или есть разница в производительности?
1 ответ
Импульс затем Ожидание почти идентичен Ожиданию затем Импульсу, так как они работают в бесконечном цикле. Pulse,Wait,Pulse,Wait почти такой же, как Wait,Pulse,Wait,Pulse
Обычно поток, ожидающий изменения, использует
Wait
и поток делает изменения используетPulse
, Поток может делать и то и другое, но общая практика зависит от обстоятельств.Данный код спит, удерживая замок. Для обучения / моделирования это нормально, но производственный код, как правило, не должен этого делать. Вы можете передать тайм-аут
Wait
что нормально и не держит блокировку во время ожидания.Вообще говоря, Monitor считается низкоуровневым примитивом синхронизации, поэтому рекомендуется использовать вместо него высокоуровневые примитивы синхронизации. Хорошо понимать низкоуровневые примитивы, такие как Monitor, но общая мудрость заключается в том, что практически для любого практического сценария доступен некоторый примитив более высокого уровня, который менее подвержен ошибкам, менее вероятен, чтобы скрыть какой-то сложный сценарий гонки, и легче читается в ком-то чужой код.