Скажем, у нас есть незаблокированная очередь с одним потоком-поставщиком и одним потоком-потребителем, и что производитель может долгое время не производить никаких данных. Было бы полезно позволить потребительскому потоку спать, когда в очереди ничего нет (для экономии энергии и освобождения ЦП для других процессов/потоков). Если бы очередь не была незаблокированной, то простой способ решить эту проблему состоит в том, чтобы поток-производитель заблокировал мьютекс, выполнил свою работу, сигнализировал условную переменную и разблокировал ее, а читающий поток заблокировал мьютекс, ожидая переменной условия. Почитай, потом разблокируй. Но если мы используем очередь без блокировки, использование мьютекса точно таким же образом устранит производительность, которую мы получаем от использования очереди без блокировки в первую очередь.
Наивное решение состоит в том, чтобы производитель после каждой вставки в очередь блокировал мьютекс, сигнализировал условную переменную, затем разблокировал мьютекс, оставляя фактическую работу (вставку в очередь) полностью вне блокировки, и чтобы потребитель делал то же самое, блокировка мьютекса, ожидание условной переменной, разблокировка ее, вытягивание всего из очереди, затем повторение, сохраняя чтение очереди вне блокировки. Однако здесь есть состояние гонки: между тем, как читатель отключается от очереди и переходит в спящий режим, производитель может вставить элемент в очередь. Теперь считыватель перейдет в спящий режим и может оставаться в нем бесконечно, пока производитель не вставит другой элемент и снова не просигнализирует переменную условия. Это означает, что иногда вы можете столкнуться с определенными предметами, которые, по-видимому, занимают очень много времени, чтобы перемещаться по очереди. Если ваша очередь всегда постоянно активна, это может не быть проблемой, но если бы она была всегда активна, вы, вероятно, могли бы полностью забыть переменную условия.
AFAICT решение состоит в том, чтобы производитель вел себя так же, как если бы он работал с обычной очередью блокировки потребностей. Он должен заблокировать мьютекс, вставить в незаблокированную очередь, сигнализировать переменную условия, разблокировать. Однако потребитель должен вести себя иначе. Когда он просыпается, он должен немедленно разблокировать мьютекс, а не ждать, пока он прочитает очередь. Затем он должен извлечь как можно большую часть очереди и обработать ее. Наконец, только когда потребитель думает о том, чтобы заснуть, он должен заблокировать мьютекс, проверить, есть ли какие-либо данные, затем, если это так, разблокировать и обработать их, а если нет, то дождаться условной переменной. Таким образом, мьютекс используется реже, чем это было бы при полной очереди, но нет риска перехода в спящий режим с данными, все еще оставшимися в очереди.
Это лучший способ сделать это? Есть ли альтернативы?
Примечание. Под «самым быстрым» я на самом деле подразумеваю «самый быстрый без выделения ядра для проверки очереди снова и снова», но это не соответствует названию ;p
Одна альтернатива: используйте наивное решение, но пусть потребитель ожидает переменную условия с тайм-аутом, соответствующим максимальной задержке, которую вы готовы допустить для элемента, проходящего через очередь. Однако, если желаемый тайм-аут довольно короткий, он может быть ниже минимального времени ожидания для вашей ОС или по-прежнему потреблять слишком много ресурсов ЦП.