线程中的内存范围共享:确保数据不会卡在缓存中

时间:2018-01-24 07:13:59

标签: c++ multithreading thread-safety

当从一个线程向另一个线程发送内存位置的地址时,如何确保数据不会卡在CPU缓存中,以及第二个线程实际读取的值是否正确? (我正在使用socketpair()发送 从一个线程到另一个线程的指针)

相关问题,c ++编译器和线程原语如何确定需要专门为同步处理的内存地址。

struct Test { int  fld; }

thread_1 ( ) {
  Test *ptr1 = new Test;
  ptr1->fld = 100;
  ::write(write_fd, &ptr1, sizeof(ptr1));
}

thread_2 () {
  Test *ptr2;
  ::read(read_fd, &ptr2, sizeof(ptr2));
  // WHAT MAGIC IS REQUIRED TO ENSURE THIS ?
  assert(ptr2->fld == 100 );
}

2 个答案:

答案 0 :(得分:2)

如果要在同一进程中的线程之间传递值,我将确保std::atomic<int>作为字段类型,以及相关的setter和getter函数。显然,将指针从一个进程传递到另一个进程根本不起作用,除非它来自内存区域,保证在两个进程中具有相同的地址 - 例如共享内存,但是你不应该需要套接字。 ..

编译器通常不知道如何处理缓存,除了atomic类型(技术上,原子通常使用单独的指令处理,而不是缓存刷新和缓存失效,以及处理器)硬件处理相关的“与其他处理器谈论缓存内容”)。

操作系统(当然会受到错误)在进程间或进程内传递时会发生这种情况。但是对于传递指针,你不能依赖它,新接收的指针值是正确的,但指针指向的内容不是缓存管理的。

在某些处理器中,您可以使用内存屏障来确定线程之间内存内容的正确顺序。这迫使处理器“在此之前执行所有内存操作”。但是,对于readwrite等系统调用,操作系统应该为您处理,并确保在read开始之前已正确写入内存读取它想要存储在套接字缓冲区中的内存,write在存储数据后会有一个内存屏障(在这种情况下是指针的值,但内存屏障会影响之前的所有读取和/或写入)那一点)。

如果您要实现自己的原语传递数据,并且处理器没有缓存一致性(大多数现代处理器都这样做),您还需要为写入端添加缓存刷新和缓存阅读方无效。这取决于体系结构,在标准C或C ++中不支持这种情况(在某些处理器中,只有OS功能[内核模式]可以刷新或使缓存内容无效,在其他处理器中它可以在用户模式代码中完成 - 此类操作的粒度也有所不同,可能需要刷新或使整个缓存系统无效,或者一次刷新32,64或128个字节的单行)

答案 1 :(得分:0)

在C ++中,您不需要关心缓存等实现细节。您唯一需要做的就是确保C ++发生后发生关系。

正如Mats Petersson的回答所示,std::atomic是实现这一目标的一种方法。对原子变量的所有访问都是有序的,尽管顺序可能不是静态确定的(即如果有两个线程试图写入同一个原子变量,则无法预测哪个写入最后发生)。

另一种强制同步的机制是std::mutex。线程可以尝试锁定互斥锁,但一次只能锁定一个互斥锁。其他线程将阻止。编译器将确保当一个线程解锁互斥锁并且下一个线程锁定互斥锁时,第一个线程的写入可以由第二个线程读取。如果这需要刷新缓存,编译器将安排。

另一种机制是std::atomic_thread_fence。如果在线程之间共享多个对象(所有对象都在同一方向上),这将非常有用。您可以将其中一个原子化,并将栅栏“附加”到该原子变量,而不是将它们全部变为原子。然后,您最后写入原子变量,并首先读取它。显然,这最好封装在一个类中。