C++ 线程中的内存范围共享:确保数据不会卡在缓存中
当将内存位置的地址从一个线程发送到另一个线程时,如何确保数据没有卡在CPU缓存中,并且第二个线程实际读取了正确的值?(我正在使用socketpair()发送 从一个线程到另一个线程的指针) <>和相关问题,C++编译器如何与线程原语一起,找出同步处理需要特别处理的内存地址。C++ 线程中的内存范围共享:确保数据不会卡在缓存中,c++,multithreading,thread-safety,C++,Multithreading,Thread Safety,当将内存位置的地址从一个线程发送到另一个线程时,如何确保数据没有卡在CPU缓存中,并且第二个线程实际读取了正确的值?(我正在使用socketpair()发送 从一个线程到另一个线程的指针) 和相关问题,C++编译器如何与线程原语一起,找出同步处理需要特别处理的内存地址。 struct Test { int fld; } thread_1 ( ) { Test *ptr1 = new Test; ptr1->fld = 100; ::write(write_fd, &
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 );
}
如果希望在同一进程中的线程之间传递值,我将确保
std::atomic
作为字段类型,并使用相关的setter和getter函数。显然,将指针从一个进程传递到另一个进程根本不起作用,除非它来自一个保证在两个进程中具有相同地址的内存区域-例如共享内存,但是您不应该需要套接字
编译器通常不知道如何处理缓存,除了原子类型(从技术上讲,原子通常使用单独的指令处理,而不是缓存刷新和缓存失效,处理器硬件处理相关的“与其他处理器谈论缓存内容”)
当在进程之间或进程内传递时,操作系统(当然会有bug)会做类似的事情。但是对于传递指针,您不能依赖于此,新接收的指针值是正确的,但是指针指向的内容不是缓存管理的
在某些处理器中,可以使用内存屏障来调整线程之间内存内容的正确顺序。这迫使处理器“在此点之前执行所有内存操作”。但是,在系统调用(如读取
和写入
)的情况下,操作系统应为您处理好这一问题,并确保在读取
开始读取它要存储在套接字缓冲区中的内存之前,内存已正确写入,而write
在存储数据后会有一个内存障碍(在本例中是指针的值,但内存障碍会影响该点之前的所有读取和/或写入)
如果要实现自己的原语来传递数据,而处理器没有缓存一致性(大多数现代处理器都有),则还需要为写入端添加缓存刷新,为读取端添加缓存失效。这是架构依赖的,在标准C或C++中没有支持(在某些处理器中,只有OS功能[内核模式])可以刷新或使缓存内容无效,在其他处理器中,它可以在用户模式代码中完成——这些操作的粒度也会发生变化,可能需要刷新或失效整个缓存系统,或者在32, 64次或128字节的单个行可以同时刷新) C++。您不需要关心缓存之类的实现细节。你唯一需要做的就是确保C++在关系之后发生。
正如Mats Peterson的回答所示,
std::atomic
是实现这一目标的一种方法。对原子变量的所有访问都是有序的,尽管顺序可能不是静态确定的(即,如果有两个线程试图写入同一个原子变量,则无法预测最后发生的写入)
另一种强制同步的机制是std::mutex
。线程可以尝试锁定互斥体,但一次只能有一个线程锁定互斥体。其他线程将阻塞。编译器将确保当一个线程解锁互斥锁,而下一个线程锁定互斥锁时,第一个线程的写操作可以被第二个线程读取。如果这需要刷新缓存,编译器将对此进行安排
另一种机制是std::atomic\u thread\u fence
。如果线程之间共享多个对象(所有对象都在同一方向上),则此选项非常有用。您可以将其中一个元素设置为原子元素,并在该原子变量上“附加”栅栏,而不是将它们全部设置为原子元素。然后最后写入原子变量,然后首先读取。显然,这最好封装在一个类中。想知道如何在原子中放置完整的结构<代码>结构测试包装器{std::atomic wrapped;
,ptr1和ptr2是*TestWrapper。原子类型仅适用于简单类型,因此您必须声明每个字段atomic
。或者执行其他操作……;)嗯。实际上,我还有一个缓冲区,发送到另一个线程以写入网络;我想知道这是否是一个通用设计!谢谢帮助。如果您有中等大小的数据(以千字节为单位)结构,那么很可能最好发送一个副本,而不是指针[显然对于没有序列化机制的类不起作用]。请注意,如果希望非一致性处理器缓存同步,则开销非常大-所花费的主要时间是将受影响的数据实际写入内存,并以缓存线大小的块在受影响的内存区域上进行迭代。因此,进行可能适合缓存的复制通常比缓存维护快。