Как вы упомянули, что вам все равно, если ваш итерирующий потребитель пропустит некоторые недавно добавленные записи, вы можете использовать список copy-on-write внизу. Это позволяет итерирующему потребителю работать с непротиворечивым моментальным снимком списка с момента его первого запуска, в то время как в других потоках обновления списка дают свежие, но непротиворечивые копии, не нарушая ни одного из существующих снимков.
Торговля здесь заключается в том, что каждое обновление списка требует блокировки для монопольного доступа достаточно долго, чтобы скопировать весь список. Эта техника склонна к тому, чтобы иметь много одновременных читателей и реже обновлять.
Попытка сначала добавить внутреннюю блокировку к контейнеру требует от вас подумать о том, какие операции должны вести себя в атомарных группах. Например, проверка, является ли список пустым, прежде чем пытаться выскочить из первого элемента, требует атомарной операции pop-if-not-empty ; в противном случае ответ на пустой список может измениться между тем, когда вызывающий абонент получает ответ и пытается действовать в соответствии с ним.
В приведенном выше примере неясно, что гарантирует выполнение итерации. Должен ли каждый элемент в списке в конечном итоге посещаться итерационным потоком? Это делает несколько проходов? Что означает, что один поток удаляет элемент из списка, пока другой поток запускает DoAction()
против него? Я подозреваю, что проработка этих вопросов приведет к значительным изменениям в дизайне.
Вы работаете в C ++, и вы упомянули о необходимости очереди с операцией pop-if-not-empty . Я написал очередь с двумя замками много лет назад с использованием ACE Library примитивов параллелизма, так как библиотека Boost thread не была еще готовый к использованию, и возможность создания стандартной библиотеки C ++, включающей такие средства, была несбыточной мечтой. Перенести его на что-то более современное было бы легко.
Эта моя очередь, называемая concurrent::two_lock_queue
- позволяет получить доступ к началу очереди только через RAII. Это гарантирует, что получение блокировки для чтения головы всегда будет сопряжено с снятием блокировки. Потребитель создает объект const_front
(постоянный доступ к элементу head), front
(неконстантный доступ к элементу head) или renewable_front
(неконстантный доступ к элементам head и преемнику) для представления исключительного право доступа к головному элементу очереди. Такие «передние» объекты не могут быть скопированы.
Class two_lock_queue
также предлагает функцию pop_front()
, которая ожидает, пока будет удален хотя бы один элемент, но в соответствии с std::queue
и std::stack
* * * * * * * * * * * * * * * * * * *
* * * * * * * * * * * * * * * * * * * * *1046* * * * * * * * * * * * * * * * * * * * * * * * 1046
В сопутствующем файле есть тип с именем concurrent::unconditional_pop
, который позволяет через RAII гарантировать, что элемент head очереди будет вытолкнут при выходе из текущей области.
Сопутствующий файл error.hh определяет исключения, возникающие при использовании функции two_lock_queue::interrupt()
, используемой для разблокировки потоков, ожидающих доступа к главе очереди.
Посмотрите на код и дайте мне знать, если вам нужно больше объяснений о том, как его использовать.