C++ 安全地将只读数据传递给新线程

C++ 安全地将只读数据传递给新线程,c++,c,multithreading,thread-safety,C++,C,Multithreading,Thread Safety,假设我有一个初始化全局变量供线程使用的程序,如下所示: int ThreadParameter; // this function runs from the main thread void SomeFunction() { ThreadParameter = 5; StartThread(); // some function to start a thread // at this point, ThreadParameter is NEVER modified

假设我有一个初始化全局变量供线程使用的程序,如下所示:

int ThreadParameter;

// this function runs from the main thread
void SomeFunction() {
    ThreadParameter = 5;

    StartThread(); // some function to start a thread
    // at this point, ThreadParameter is NEVER modified.
}

// this function is run in a background worker thread created by StartThread();
void WorkerThread() {
    PrintValue(ThreadParameter); // we expect this to print "5"
}
这些问题应该适用于可能遇到的任何通用处理器体系结构。我希望解决方案是可移植的,而不是特定于具有更强内存保证的体系结构,如x86

  • 一般性问题:尽管这种情况非常普遍,但在所有处理器体系结构中都安全吗?如果不安全,如何使其安全
  • 全局变量不是易变的;在调用
    StartThread()
    之后,它是否可能会被重新排序,并让我保持状态?如何解决这个问题
  • 假设计算机有两个具有自己缓存的处理器。主线程在第一个处理器上运行,工作线程在第二个处理器上运行。假设在程序开始运行
    SomeFunction()
    之前,包含
    ThreadParameter
    的内存块已被分页到每个处理器的缓存中
    SomeFunction()
    5
    写入
    ThreadParameter
    ,该线程存储在第一个处理器的缓存中,然后启动在第二个处理器上运行的工作线程。第二个处理器上的
    WorkerThread()
    是否会看到
    ThreadParameter
    的未初始化数据,而不是预期的
    5
    ,因为第二个处理器中的内存页尚未看到来自第一个处理器的更新
  • 如果需要一些不同的东西-如果不是简单的
    int
    ,我可以使用指向不一定在多线程环境中使用的更复杂数据类型的指针,如何最好地处理这个问题

  • 如果我的担心没有根据,那么我不需要担心的具体原因是什么?

    创建新线程时,线程的构造与线程函数的开始同步。这意味着您做得很好-在创建线程之前写入ThreadParameter,线程在启动后访问它,因此您可以确保写入发生在读取之前,因此线程可以确保看到正确的值


    (编译器需要确保在启动线程之前完成的所有写入操作在新线程中可见。)

    根据您的描述,在启动任何子线程之前,您似乎正在写入ThreadParameter(或某些其他数据结构),并且您将永远不会再写入ThreadParameter。。。它的存在是为了根据需要读取,但在初始化后不再更改;对吗?如果是这样,那么就没有必要在每次子线程想要读取数据时,甚至在第一次读取数据时使用任何线程同步系统调用(或处理器/编译器原语)

    挥发性物质的处理有一定的编译器特异性;我知道,至少对于PowerPC的Diab,有一个编译器选项可以处理volatile:在每次读/写变量之后使用PowerPC EIEIO(或MBAR)指令,或者不使用它。。。除此之外,还禁止与变量关联的编译器优化。(EIO/MBAR是PowerPC的指令,用于禁止处理器本身对I/O进行重新排序;即,指令之前的所有I/O必须在指令之后的任何I/O之前完成)

    从正确性/安全性的角度来看,将其声明为易失性并没有坏处。但从实用的角度来看,如果您在StartThread()之前初始化ThreadParameter,则实际上不需要将其声明为volatile(不这样做会加快所有后续访问)。几乎任何实质性的函数调用(例如,可能是printf()或cout,或任何系统调用等)都会发出数量级以上的指令,以确保处理器在调用StartThread()之前不会很久就处理write to thread参数。实际上,StartThread()本身几乎肯定会在所讨论的线程实际启动之前执行足够的指令。因此,我建议您实际上不需要将其声明为volatile,即使您在调用StartThread()之前立即初始化它也可能不需要

    现在,关于您的问题,如果在运行主线程的处理器执行初始化之前,包含该变量的页面已经加载到两个处理器的缓存中,会发生什么情况:如果您使用的是一个具有类似CPU的通用平台,硬件应该已经准备好为您处理缓存一致性。在通用平台上,无论是否是多处理器,当处理器有单独的指令和数据缓存,并且您编写自修改代码时,您都会遇到缓存一致性问题:写入内存的指令与数据无法区分,因此,CPU不会使指令缓存中的这些位置无效,因此指令缓存中可能有过时的指令,除非随后使指令缓存中的这些位置无效(要么发布自己的特定于处理器的汇编指令(根据操作系统和线程的权限级别,可能不允许您执行此操作),要么为操作系统发出适当的缓存使系统调用无效)。但您所描述的不是自修改代码,因此在这方面您应该是安全的

    您的问题1询问了如何在所有处理器体系结构中确保安全。正如我前面所讨论的,如果您使用的是数据总线正确桥接的同类处理器,则应该是安全的。为多处理器互连设计的通用处理器具有总线嗅探协议,可以检测对共享内存的写入…只要因为线程库正确配置了共享内存区域。如果您在嵌入式系统中工作,您可能需要自己在yo中配置