C++11 无锁包算法的最优内存排序
在以下无锁行李的C++11编码算法中,NQFENCE和DQFENCE内存屏障的最佳设置是什么 描述:此算法是一个(否则)接近最优的多生产者多消费者无锁队列,用于向线程池提供非空指针。它的正确性非常明显(模错误!)。它是不可线性化的,也就是说,由单个线程排队的数据可能会无序退出队列,因此它不适用于单个使用者队列(或使用者不独立的环境) 令人惊讶的是(至少对我来说!)它看起来几乎是最优的(总体而言)(无论这意味着什么)。它还有一些非常糟糕的特性,例如写入线程可能无法无限期地将数据排队,以及除了一个写入线程之外的所有写入线程都可能在不同的CPU上运行,从而无限期地暂停这些CPU。然而,该属性似乎是通用的(对于所有可能的实现都是如此)。读者也一样 说明:出列操作从插槽0开始,并用空指针交换该插槽中的值。如果结果为非NULL,则返回它,并在那里粘贴NULL。否则,我们将NULL替换为NULL,因此我们增加插槽索引,并在睡眠后重试 排队操作也从插槽0开始,并将数据交换到存储位置,使其具有该位置的值。如果结果为空,则表示我们已完成工作并返回。否则,我们错误地替换了其他指针,所以我们增加索引,稍微休眠,然后继续尝试将该值放回队列 我们不跟踪头部或尾部位置,因为这将需要额外的约束,并损害非竞争性运营中的绩效。当存在争用时,可能需要额外的时间在阵列中搜索合适的插槽,但是这可能是可取的 我们可以使用近似的头跟踪和尾跟踪:这将涉及到索引位置的原子读写,并具有宽松(即,无)的内存顺序。原子性仅用于确保写入整个值。这些指标可能不准确,但这不会影响算法的正确性。然而,并不完全清楚这种修改实际上会提高性能 该算法很有趣,因为与其他复杂算法不同,每个入队和出队方法只需要一个CAS操作C++11 无锁包算法的最优内存排序,c++11,atomic,lock-free,C++11,Atomic,Lock Free,在以下无锁行李的C++11编码算法中,NQFENCE和DQFENCE内存屏障的最佳设置是什么 描述:此算法是一个(否则)接近最优的多生产者多消费者无锁队列,用于向线程池提供非空指针。它的正确性非常明显(模错误!)。它是不可线性化的,也就是说,由单个线程排队的数据可能会无序退出队列,因此它不适用于单个使用者队列(或使用者不独立的环境) 令人惊讶的是(至少对我来说!)它看起来几乎是最优的(总体而言)(无论这意味着什么)。它还有一些非常糟糕的特性,例如写入线程可能无法无限期地将数据排队,以及除了一个写
#include <atomic>
#include <stdlib.h>
#define NQFENCE ::std::memory_order_cst
#define DQFENCE ::std::memory_order_cst
struct bag {
::std::atomic <void *> volatile *a;
size_t n;
bag (size_t n_) : n (n_),
a((::std::atomic<void*>*)calloc (n_ , sizeof (void*)))
{}
void enqueue(void *d)
{
size_t i = 0;
while
(
(d = ::std::atomic_exchange_explicit(a + i, d,
NQFENCE))
)
{
i = (i + 1) % n;
SLEEP;
}
}
void *dequeue ()
{
size_t i = 0;
void *d = nullptr;
while
(
!(d = ::std::atomic_exchange_explicit(a + i, d,
DQFENCE))
)
{
i = (i + 1) % n;
SLEEP;
}
return d;
}
};
#包括
#包括
#定义NQFENCE::std::内存\u顺序\u cst
#定义DQFENCE::std::内存\u顺序\u cst
结构袋{
::std::原子挥发性*a;
尺寸;
袋子(尺寸):n(n),
a((::std::atomic*)calloc(n_,sizeof(void*))
{}
无效排队(无效*d)
{
尺寸i=0;
虽然
(
(d=::std::原子交换显式(a+i,d,
NQFENCE)
)
{
i=(i+1)%n;
睡觉
}
}
void*出列()
{
尺寸i=0;
void*d=nullptr;
虽然
(
!(d=::std::原子交换显式(a+i,d,
DQFENCE)
)
{
i=(i+1)%n;
睡觉
}
返回d;
}
};
如果外部代码(例如,printf(“值:%p”,值);
)按“原样”使用存储在袋子中的值
),则不需要内存顺序约束;也就是说,NQFENCE
和DQFENCE
可能只是:std::memory\u order\u released
否则,(例如,value
是指向结构/对象的指针,哪些字段有意义),NQFENCE
应该是::std::memory\u order\u release
,以确保在发布对象之前初始化对象的字段。至于DQFENCE
,它可以是::std::memory\u order\u consumer
,在带有字段的简单对象中,因此每个值的字段都将在值本身之后提取。在常见情况下,::std::memory\u order\u acquire
应用于DQFENCE
。所以,生产者在发布值之前执行的每个内存操作都会被消费者看到
当谈到性能时,仅在enqueue()中的第一次迭代中使用NQFENCE
就足够了,其他迭代可以安全地使用::std::memory\u order\u relaxed
:
void enqueue(void *d)
{
size_t i = 0;
if(d = ::std::atomic_exchange_explicit(a + i, d,
NQFENCE))
{
do
{
i = (i + 1) % n;
SLEEP;
} while(d = ::std::atomic_exchange_explicit(a + i, d,
::std::memory_order_relaxed));
}
}
类似地,只有dequeue()
中的最后一次迭代需要DQFENCE
。由于最后一次迭代只能在原子操作后检测到,所以这种情况下没有通用的优化。您可以使用附加围栏代替内存顺序:
void *dequeue ()
{
size_t i = 0;
void *d = nullptr;
while
(
!(d = ::std::atomic_exchange_explicit(a + i, d,
::std::memory_order_relaxed))
)
{
i = (i + 1) % n;
SLEEP;
}
::std::atomic_thread_fence(DQFENCE);
return d;
}
如果原子交换显式
在松弛顺序下实际上更快,则此操作将获得性能,但如果此操作已经暗示顺序排序(请参见Anton的),则此操作将失去性能。此问题仅适用于非英特尔硬件,因为在英特尔硬件上,CAS具有memory\u order\u cst
语义。我正在构建一个独立于平台的产品,所以它很重要。例如,它可能(有一天!)在手臂上跑步。好吧,这是有道理的,但我还是有点不安。围栏不在那里,因此客户端线程“可以看到对象的其余部分”,事实上,我甚至没有想到这一点,应该这样做。我担心的是,制作人在袋子里放了一个其他制作人和消费者都能看到的条目,否则算法就无法工作。类似地,当消费者退出队列时,会放置一个NULL来删除元素,并且必须看到该元素。例如,对于宽松的排序,为什么两个空值的CA不能同时获取相同的非空值?[注释太短:]因此,如果Thr1不执行CAS addr,NULL并获取V not NULL,为什么Thr2不执行CAS addr,NULL并获取V not NULL?原子性确保线程和写入整个值,并且两个CA必须序列化,但如果内存顺序放松,什么确保CAS#1写入的NULL将由CAS#2返回?这是任何CAS操作的特征,任何两个CAS操作都不能读取相同的值。C++标准的第27.3.12段说: