EDIT:
ПРОЩЕ:
Я предлагаю вам не нужна голова и хвост для вашей очереди. Просто есть голова. Если head = NULL, список пуст. Добавьте предметы в голову. Удалить предметы из головы. Проще, меньше операций CAS.
ХЕЛПЕР:
В комментариях я предложил вам подумать о вспомогательной схеме для управления гонкой. В моей версии, что означает «блокировка свободна», нормально иметь редкие условия гонки, если они не вызывают проблем. Мне нравится дополнительная производительность по сравнению с тем, что бездействующий поток спит пару мс слишком долго.
Вспомогательные идеи. Когда потребитель берет работу, он может проверить, есть ли нить в коме. Когда продюсер добавляет работу, он может искать нити в коме.
Так что следите за шпалами. Используйте связанный список спящих. Когда поток решает, что работы нет, он помечает себя как! Awake и сам CAS, возглавляющий список спящих. Когда получен сигнал на пробуждение, поток помечает себя как пробужденный. Затем вновь пробудившаяся нить очищает список спящих. Чтобы очистить одновременный единый связанный список, вы должны быть осторожны. Вы можете только CAS в голову. Таким образом, пока глава списка спящих помечена как активная, вы можете отключить ее. Если голова не проснулась, продолжайте сканировать список и «ленивая отсоединить» (я придумал этот термин) оставшиеся пробужденные предметы. Lazy unlink - это просто ... просто установите следующий ptr предыдущего предмета поверх пробужденного предмета. Одновременное сканирование все равно попадет в конец списка, даже если оно попадет в объекты, которые находятся в пробуждении. Последующие сканы видят более короткий список. Наконец, каждый раз, когда вы добавляете работу или выполняете работу, отсканируйте список спящих! Если потребитель замечает, что работа остается после захвата некоторой работы (.next work! = NULL), потребитель может просмотреть список спящих и сообщить о первом потоке! После того, как продюсер добавляет работу, продюсер может просмотреть список спящих и сделать то же самое.
Если у вас есть сценарий широковещательной рассылки, и вы не можете сигнализировать об одном потоке, просто сохраняйте количество спящих потоков. Несмотря на то, что это число все еще> 0, потребитель, замечающий оставшуюся работу, и потребитель, добавляющий работу, будут транслировать сигнал на пробуждение.
В нашей среде у нас есть 1 поток на SMT, поэтому список спящих никогда не может быть таким большим (ну, если я не получу в руки одну из этих 128 машин с параллельными потоками!) Мы создаем рабочие элементы на ранних этапах транзакции. В первую секунду мы можем сгенерировать 10000 рабочих элементов, и это производство быстро сойдет на нет. Потоки работают на пару секунд на этих рабочих элементах. Таким образом, у нас редко есть поток в пуле ожидания.
ВЫ МОЖЕТЕ ИСПОЛЬЗОВАТЬ ЗАМКИ
Если у вас есть только 1 поток и вы редко создаете работу ... это не сработает для вас. В этом случае производительность мьютексов не имеет значения, и вы должны просто использовать их. Используйте блокировку очереди спящего в этом сценарии. Думайте о свободе от блокировки, как о том, что «никаких замков не существует».
ПРЕДЫДУЩАЯ ПОЧТА:
Ты говоришь:
Есть очередь на работу.
Есть много потребительских тем.
Потребитель должен взять работу и сделать это, если есть работа
Потребительский поток должен спать, пока не будет работы.
Если вы, мы делаем это, используя только атомарные операции:
Очередь работы представляет собой связанный список. Также есть связанный список спящих тем.
Чтобы добавить работу: CAS глава списка к новой работе. Когда работа добавлена, мы проверяем, есть ли какие-либо темы в списке спящих. Если есть, прежде чем добавлять работу, мы исключаем спящего из списка спящих, устанавливаем его работу = новую работу и затем просим спящего проснуться. Мы добавляем работу в очередь работ.
Чтобы потреблять работу: CAS от главы списка к голове-> далее. Если заголовок рабочего списка равен NULL, мы отправляем поток в список спящих.
Как только у потока есть рабочий элемент, поток должен преобразовать состояние рабочего элемента в WORK_INPROGRESS или что-то подобное. Если это не удается, это означает, что работа выполняется другим, поэтому поток потребителя возвращается к поиску работы. Если поток просыпается и имеет рабочий элемент, он все равно должен сохранять состояние CAS.
Так что, если работа добавляется, спящий потребитель всегда просыпается и вручает работу. pthread_kill () всегда пробуждает поток в sigwait (), потому что даже если поток получает сигнал sigwait после сигнала, сигнал получен. Это решает проблему того, что поток помещает себя в список спящих, но получает сигнал перед сном. Все, что происходит, - поток пытается владеть своей -> работой, если она есть. Если у вас нет собственной работы или нет работы, поток возвращается в режим потребления. Если потоку не удается выполнить CAS в списке спящих, это означает, что его побьет другой поток или что производитель снял спящий. В целях безопасности у нас есть нить, действующая так, как будто она только что проснулась.
У нас нет условий для гонки, у нас есть несколько производителей и потребителей. Мы также смогли расширить это, чтобы позволить нитям спать на отдельных рабочих элементах.