Помимо простых случаев функций InterlockedXxx, кажется, что преобладающий шаблон для всех них заключается в том, что они реализуют свои собственные блокировки.
Ни один из приведенных здесь ответов, похоже, не раскрывает суть разницы между «без блокировки» цикл CAS и мьютекс или спин-блокировка.
Важным отличием является то, что неблокирующие алгоритмы гарантированно работают самостоятельно — без помощи других потоков. При блокировке или круговой блокировке любой плохой поток, который не может получить блокировку, полностью оказывается во власти потока, которому принадлежит блокировка. Плохой поток, который не может получить блокировку, не может ничего сделать, кроме ожидания (либо через ожидание занятости, либо через спящий режим с помощью ОС).
С алгоритмами без блокировок, которые зацикливаются на CAS, каждый поток гарантированно продвигается вперед независимо от того, что делают другие конкурирующие потоки. Каждая нить, по сути, контролирует свою судьбу. Да, возможно, ему все еще придется зацикливаться много раз, но количество циклов ограничено количеством конкурирующих потоков. По большей части он не может бесконечно зацикливаться. (На практике возможна активная блокировка, например, из-за LL/SC , который продолжает давать сбой из-за ложного совместного использования) - но опять же меры могут быть приняты самим потоком, чтобы справиться с этим - он не зависит от милости другого потока, удерживающего блокировку.
Что касается производительности, это зависит. Я видел вопиющие примеры того, как алгоритмы без блокировок полностью уступают своим аналогам с блокировками даже в условиях высокой конкуренции потоков. На машине x86-64 с Debian 7 я сравнил производительность между очередью C++ Boost.Lockfree (на основе алгоритма Майкла/Скотта) и обычным старым std::queue
окружением std::mutex
. В условиях высокой конкуренции потоков версия без блокировки была почти в два раза медленнее.
Так почему же? Что ж, производительность алгоритмов без блокировки в конечном итоге сводится к деталям реализации. Как алгоритм избегает ABA? Как это обеспечивает безопасное восстановление памяти? Существует так много вариантов... помеченные указатели, освобождение на основе эпохи, состояние RCU/неактивное состояние, указатели опасностей, общая сборка мусора в масштабе всего процесса и т. д. Все эти стратегии влияют на производительность, а некоторые также накладывают ограничения на то, как ваше приложение в целом можно спроектировать. В целом, по моему опыту, подходы с подсчетом ссылок (или подходы с тегированными указателями), как правило, работают плохо. Но альтернативы могут быть гораздо более сложными для реализации и требуют гораздо большей инфраструктуры высвобождения памяти, основанной на локальном хранилище потока или обобщенной сборке мусора.
person
Charles Salvia
schedule
07.05.2016