我有一个问题,我需要了解是否有更好的解决方案。我编写了以下代码,以将一些变量从编写器线程传递到读取器线程。这些线程固定到共享相同L2高速缓存(禁用的超线程)的不同CPU。
writer_thread.h
struct a_few_vars {
uint32_t x1;
uint32_t x2;
uint64_t x3;
uint64_t x4;
} __attribute__((aligned(64)));
volatile uint32_t head;
struct a_few_vars xxx[UINT16_MAX] __attribute__((aligned(64)));
reader_thread.h
uint32_t tail;
struct a_few_vars *p_xxx;
writer线程增加head变量,而reader线程检查head变量和tail变量是否相等。如果它们不相等,则它将按以下方式读取新数据
while (true) {
if (tail != head) {
.. process xxx[head] ..
.. update tail ..
}
}
到目前为止,性能是最重要的问题。我使用的是Intel Xeon处理器,读取器线程每次都从内存中获取head值和xxx [head]数据。我使用对齐的数组是无锁的
就我而言,有什么方法可以尽快将变量刷新到读取器CPU缓存中。是否可以从写入器CPU触发读取器CPU的预取。如果存在,我可以使用__asm__使用特殊的Intel指令。总之,在固定到不同CPU的线程之间传递结构中的变量的最快方法是什么?
预先感谢
答案 0 :(得分:4)
根据C11,一个线程写一个volatile
变量而另一个线程读取它的行为是不确定的。 volatile
个访问也未相对于其他访问进行排序。您希望写入器中的atomic_store_explicit(&head, new_value, memory_order_release)
和读取器中的atomic_load_explicit(&head, memory_order_acquire)
创建acq / rel同步,并强制编译器使存储在您结构中的存储在head
之前可见,这表明读者有新数据。
({tail
是读取器线程专用的,因此写入器没有机制等待读取器看到新数据再写入更多数据。因此,从技术上讲,如果写入器存在结构内容上的竞争,线程在读者仍在读取的同时再次写入。因此该结构也应为_Atomic
)。
您可能需要一个seq锁,在其中写入器更新序列号,并在复制变量后读取器在和之前对其进行检查。 https://en.wikipedia.org/wiki/Seqlock可以让您在极少数情况下(当读取器复制数据时写入器处于更新过程中)检测并重试。
这对于只写/只读情况非常有用,尤其是在您不必担心读者错过更新的情况下。
how to implement a seqlock lock using c++11 atomic library
GCC reordering up across load with `memory_order_seq_cst`. Is this allowed?显示了另一个示例(此示例导致gcc错误)。
将这些从C ++ 11 std :: atomic移植到C11 stdatomic应该很简单。确保使用atomic_store_explicit
,因为普通atomic_store
的默认内存顺序为memory_order_seq_cst
,这会比较慢。
您实际上无能为力,可以加快编写器的速度,使其在全球范围内可见。一个CPU内核已经尽可能快地将存储从其存储缓冲区提交到其L1d(遵守x86内存模型的限制,不允许对StoreStore进行重新排序)。
在Xeon上,请参见When CPU flush value in storebuffer to L1 Cache?,以了解有关不同侦听模式及其对单个套接字内内核间延迟的影响的一些信息。
使用MESI保持一致性,多个内核上的缓存是一致的。
读者可能需要做的最好的工作就是在原子变量上进行自旋等待,在自旋循环内使用_mm_pause()
可以避免退出自旋循环时清除内存顺序错误的推测管道。
您也不想在写入过程中醒来而必须重试。您可能希望将seq-lock计数器与数据放在同一缓存行中,以便可以将这些存储区合并到写入核心的存储缓冲区中。