Почему имя lockFREE?это говорит о том, что нельзя (мьютекс) заблокировать?
Конечно, все что угодно может быть заблокировано;вы помещаете мьютекс за пределы структуры данных, и каждый поток, касающийся структуры данных, использует его.
boost::lockfree::queue
обеспечивает unsynchronized_pop
и unsynchronized_push
для использования в случаях, когда вы гарантировали, что только один потокможет получать доступ к очереди.
Но основная цель lockfree::queue
и алгоритмов / структур данных без блокировки состоит в том, что они не должны быть заблокированы: несколько потоков могутбезопасно писать и / или читать одновременно.
«без блокировки» имеет 2 значения в программировании , что приводит к потенциально запутанным, но истинным утверждениям, таким как «без блокировки»Алгоритм не свободен от блокировки ".
Случайное использование: синоним для блокировки без блокировки - реализовано без мьютексов, с использованием атомарных загрузок, хранилищ и операций RMW, таких как CAS или std::atomic::atomic_fetch_add
.См. Например Введение в программирование без блокировки (Джефф Прешинг).И, возможно, Что должен знать каждый системный программист о параллелизме .
std::shared_ptr
использует атомарные операции без блокировки для управления своим блоком управления.C ++ 11 std::atomic<>
предоставляет примитивы без блокировки для пользовательских алгоритмов.См. стандартное .Обычно в C ++ 11 несинхронизированный доступ к одной и той же переменной несколькими потоками является неопределенным поведением.(Если только они не предназначены только для чтения.) Но std::atomic
дает вам четко определенное поведение с вашим выбором последовательной согласованности, получения / освобождения или упорядоченного упорядочения памяти.
Техническое значение для информатики: нить, спящая вечно или убитая, не блокирует остальные нити .т.е. гарантированный прогресс вперед для программы в целом (хотя бы один поток).(Ожидание - это когда потоки никогда не должны повторяться).См. https://en.wikipedia.org/wiki/Non-blocking_algorithm. Цикл повторения CAS является классическим примером без блокировки, но не без ожидания.Без ожидания это такие вещи, как потоки чтения RCU (чтение-копирование-обновление) или, в зависимости от определений, atomic_fetch_add
на оборудовании, которое реализует его как примитив (например, x86 xadd
), а не в терминах LL / SCили цикл повторных попыток CAS.
Большинство многолинейных / многозаписывающих очередей без блокировки в техническом смысле не имеют блокировки. Обычно они используют кольцевой буфер,и писатель каким-то образом «потребует» запись (установив порядок в очереди).но он не может быть прочитан, пока писатель не завершит запись в саму запись.
См. Гарантии прогресса без блокировки для примера с анализом возможной блокировкиповедение.Средство записи атомарно увеличивает индекс записи, затем записывает данные в запись массива.Если писатель спит между этими действиями, другие писатели могут заполнить более поздние записи, пока читатели застряли в ожидании этой заявленной, но не письменной записи.(Я не смотрел на boost::lockfree::queue<T>
, но, по-видимому, это похоже 1 .)
На практике производительность отличная при очень низком уровне разногласий между писателями и читателями.Но в теории писатель может заблокировать в самый неподходящий момент и остановить всю очередь.
Сноска 1: Другой вероятный вариант для очереди - это связанный список.В этом случае вы можете полностью построить новый узел и затем попытаться внести его в список.Так что, если вам удастся добавить его, тогда другие потоки смогут прочитать его сразу, потому что у вас правильно установлены указатели.
Но проблема восстановления (безопасное освобождение памяти, которую могут читать другие потоки, чтобы увидеть, если другой читатель имеетуже утверждал их) чрезвычайно тернистый вне языков / сред сбора мусора.(например, Java)
boost::lockfree::queue<int> queue(128);
Почему 128?
Это размер очереди (максимальный) в записях.int
в данном случае, потому что вы использовали queue<int>
, хм.Как упомянуто выше, большинство очередей без блокировки использует кольцевой буфер фиксированного размера.Он не может перераспределять и копировать как std :: vector, когда ему нужно расти, потому что другие потоки могут читать его одновременно.
Как описано в руководстве (первый гугл дляboost::lockfree::queue
), конструктор explicit queue(size_type)
принимает размер.
Вы также можете вставить емкость в тип, используя ее в качестве параметра шаблона.(Таким образом, емкость становится константой времени компиляции везде, где используется очередь, а не только в местах, которые могут распространять константы из вызова конструктора.)
Класс, очевидно, не применяет / не требуетразмером-2, поэтому параметр размера шаблона может быть значительно лучше оптимизирован, если % capacity
скомпилировать операции в AND с маской вместо деления.