Увеличение счетчика ссылок всегда можно сделать с помощью<memory_order_relaxed>: Новые ссылки на объект могут быть сформированы только из существующей ссылки, и передача существующей ссылки из одной нити в другую уже должна обеспечивать любую требуемую синхронизацию.
Важно обеспечить любой возможный доступ к объекту в одной нити (через существующую ссылку) к, прежде чемудалять объект в другой нити. Это достигается операцией «освобождение» после сброса ссылки (любой доступ к объекту через эту ссылку должен, очевидно, произойти раньше), и операцией «приобретение» перед удалением объекта.
Можно было бы использовать<memory_order_acq_rel>для операции<fetch_sub>, но это приводит к ненужным операциям «приобретения», когда эталонный счетчик еще не достигает нуля и может налагать штраф за производительность.
Цель шпин-блокировкисостоит в том, чтобы предотвратить одновременное обращение нескольких потоков к общей структуре данных. В отличие от mutex, потоки будут загружены и тратить циклы процессора вместо того, чтобы уступить процессор другому потоку.Не используйте веретена, если вы не уверены, что понимаете последствия.
Цель блокировки — убедиться, что один доступ к общей структуре данных всегда строго «случается раньше» другого. Использование приобретения / выпуска в блокировке / разблокировке требуется и достаточно, чтобы гарантировать этот заказ.
Правильно было бы написать операцию «замок» следующим образом:
Однако эта «оптимизация» а) бесполезна, а b) может на самом деле навредить: а) Поскольку поток будет активно вращаться на блокировке, не имеет значения, будет ли он тратить циклы процессора только на операции «обмена» или на бесполезные операции «обмена» и «приобретения». b) Жесткий цикл «обмена» без какой-либо команды синхронизации памяти, введенной посредством операции «приобретение», монополизирует подсистему памяти и ухудшает производительность других компонентов системы.
ЦельСинглтон с двойной проверкой схемы блокировкидолжен гарантировать, что в лучшем случае один экземпляр конкретного объекта создается. Если один экземпляр уже создан, доступ к существующему объекту должен быть максимально легким.
Мутекс гарантирует, что только один экземпляр объекта когда-либо создан. Метод<instance>должен гарантировать, что любое отклонение объекта строго «происходит после» создания экземпляра в другой нити. Использование<memory_order_release>после создания и инициализации объекта и<memory_order_consume>до отсылки к объекту обеспечивает эту гарантию.
Было бы допустимо использовать<memory_order_acquire>вместо<memory_order_consume>, но это обеспечивает более сильную гарантию, чем требуется, поскольку заказывать нужно только операции в зависимости от значения указателя.
Кольцевой буфербез ожиданияобеспечивает механизм ретрансляции объектов из одной нити «производитель» в одну нить «потребитель» без каких-либо замков. Операции на этой структуре данных являются «без ожидания», что означает, что каждая операция завершается в пределах постоянного количества шагов. Это делает эту структуру данных пригодной для использования в жестких системах реального времени или для связи с обработчиками прерываний / сигналов.
ringbuffer<int,32>r;// try to insert an elementif(r.push(42)){/* succeeded */}else{/* buffer full */}// try to retrieve an elementintvalue;if(r.pop(value)){/* succeeded */}else{/* buffer empty */}
Реализация гарантирует, что кольцевые индексы не «обходят» друг друга, чтобы гарантировать, что никакие элементы не будут потеряны или прочитаны дважды.
Кроме того, он должен гарантировать, что доступ к чтению для конкретного объекта в<pop>"происходит после" он был написан в<push>. Это достигается написанием<head_>с «освобождением» и чтением его с «приобретением». И наоборот, реализация также гарантирует, что доступ считывания к определенному кольцевому элементу «происходит раньше», прежде чем переписывать этот элемент с новым значением, получая доступ<tail_>с соответствующими ограничениями упорядочения.
Цель очередейбез ожидания для нескольких производителейсостоит в том, чтобы позволить произвольному числу производителей запрашивать объекты, которые извлекаются и обрабатываются в порядке FIFO одним потребителем.
template<typenameT>classwaitfree_queue{public:structnode{Tdata;node*next;};voidpush(constT&data){node*n=newnode;n->data=data;node*stale_head=head_.load(boost::memory_order_relaxed);do{n->next=stale_head;}while(!head_.compare_exchange_weak(stale_head,n,boost::memory_order_release));}node*pop_all(void){T*last=pop_all_reverse(),*first=0;while(last){T*tmp=last;last=last->next;tmp->next=first;first=tmp;}returnfirst;}waitfree_queue():head_(0){}// alternative interface if ordering is of no importancenode*pop_all_reverse(void){returnhead_.exchange(0,boost::memory_order_consume);}private:boost::atomic<node*>head_;};
waitfree_queue<int>q;// insert elementsq.push(42);q.push(2);// pop elementswaitfree_queue<int>::node*x=q.pop_all()while(x){X*tmp=x;x=x->next;// process tmp->data, probably delete it afterwardsdeletetmp;}
Реализация гарантирует, что все объекты, стоящие в очереди, обрабатываются в том порядке, в котором они были поставлены в очередь, создавая единый список объектов в порядке обратной обработки. Очередь атомарно опорожняется потребителем и приводится в правильный порядок.
Необходимо гарантировать, что любой доступ к объекту, который будет поставлен в очередь производителем, «происходит раньше» любого доступа потребителя. Это обеспечивается путем вставки объектов в список свысвобождениеми очерчивания их спотреблениемпорядка памяти. Необязательно использовать, чтобы получитьпорядок памяти в<waitfree_queue::pop_all>, потому что все задействованные операции зависят от значения атомного указателя посредством отсчета.
Статья Usage examples раздела The Boost C++ Libraries BoostBook Documentation Subset Chapter 6. Boost.Atomic может быть полезна для разработчиков на c++ и boost.
Материалы статей собраны из открытых источников, владелец сайта не претендует на авторство. Там где авторство установить не удалось, материал подаётся без имени автора. В случае если Вы считаете, что Ваши права нарушены, пожалуйста, свяжитесь с владельцем сайта.