以下用于无锁包的C ++ 11编码算法中NQFENCE和DQFENCE内存屏障的最佳设置是什么?
描述:该算法是一个(否则)接近最优的多生产者多消费者无锁队列,用于为非空指针提供线程池。它的正确性非常明显(模数错误!)。它不是可线性化的,也就是说,由单个线程排队的数据可能无序排队,因此它不适合单个消费者队列(或消费者不是独立的环境)。
令人惊讶的是(至少对我来说!)它似乎几乎是最佳的(一般而言)(无论这意味着什么)。它还有一些非常讨厌的属性,例如写入程序线程无法无限地排队数据的可能性,以及除了一个写入程序线程在不同CPU上运行的可能性无限期地停止这些CPU。然而,该属性看似普遍(对于所有可能的实现都是如此)。同上读者。
说明:dequeue操作从插槽0开始,并在那里交换NULL指针。如果结果为非NULL,则返回它,在那里粘贴了NULL。否则我们将NULL换成NULL,所以我们递增槽索引并在睡眠后重试。
排队操作也从插槽零开始,并交换数据以存储其中的值。如果结果为NULL,我们已完成工作并返回。否则,我们错误地替换了一些其他指针,所以我们递增索引,稍微睡一会,然后尝试将该值放回队列中继续。
我们不会跟踪头部或尾部位置,因为这会在无竞争操作中需要额外的约束和损害性能。当存在争用时,可能需要额外的时间来搜索阵列以寻找合适的时隙,但这可能是合乎需要的!
我们可以使用近似的头部和尾部跟踪:这将涉及原子读取和写入索引位置,放松(即没有)内存排序。原子性只需要确保写入整个值。这些指数不准确,但这不会影响算法的正确性。然而,并不完全清楚这种修改实际上会提高性能。
这个算法很有意思,因为与其他复杂算法不同,每个入队和出队方法只需要一个CAS操作。
#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;
}
};
答案 0 :(得分:2)
如果外部代码(例如value
)“按原样”使用存储在包中的printf("Value: %p", value);
,则不需要内存顺序约束;同时NQFENCE
和DQFENCE
可能只是::std::memory_order_relaxed
。
否则,(例如value
是指向struct / object的指针,哪些字段有一个),NQFENCE
应该是::std::memory_order_release
,以确保在发布之前初始化对象的字段物体。至于DQFENCE
,在简单的object-with-fields情况下它可以是::std::memory_order_consume
,因此每个值的字段将在之后获取值本身。在常见情况下::std::memory_order_acquire
应该用于DQFENCE
。因此,消费者可以看到生产者在发布价值之前执行的每个记忆操作。
在讨论性能时,只需 类似地, 如果NQFENCE
在enqueue()
的第一次迭代 就可以了,其他迭代可以安全地使用::std::memory_order_relaxed
:< / p>
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
。由于最后一次迭代只能在原子操作之后检测到,因此对于这种情况没有通用优化。您可以使用其他 fence 而不是内存顺序: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;
}
atomic_exchange_explicit
在宽松的顺序下实际上更快,这将获得性能,但如果此操作已经暗示顺序排序(参见Anton的comment),则会失去性能。