Посмотрим еще раз на добавление задачи в очередь:
lock (locker) { taskQ.Enqueue(task); Monitor.PulseAll(locker); } |
Вообще говоря, можно было сэкономить на выдаче сигналов, сигналя только тогда, когда действительно есть возможность освободить блокированного исполнителя:
lock (locker) { taskQ.Enqueue(task); if (taskQ.Count <= workers.Length) Monitor.PulseAll(locker); } |
Сэкономим мы, однако, немного, так как выдача сигнала занимает менее микросекунды и не влечет за собой накладных расходов для занятых потребителей, поскольку они этот сигнал все равно игнорируют. Правильной практикой для многопоточного кода будет удаление любой необязательной логики: плохо воспроизводимый дефект из-за глупой ошибки – слишком большая цена за экономию одной микросекунды! Вот пример внесения блуждающей ошибки типа “застрявший потребитель”, которая наверняка не будет выявлена при первоначальном тестировании (найди одно отличие):
lock (locker) { taskQ.Enqueue(task); if (taskQ.Count < workers.Length) Monitor.PulseAll(locker); } |
Сигнализация без всяких условий защитит вас от этого типа ошибок.
СОВЕТ Сомневаешься – сигналь! В рассмотренном шаблоне проектирования сигнализация редко может повредить. |