5

在 Microsoft Visual C++ 中,我可以调用 CreateThread() 来通过一个void *参数启动一个函数来创建一个线程。我将一个指向结构的指针作为该参数传递,我看到很多其他人也这样做。

我的问题是,如果我传递一个指向我的结构的指针,我怎么知道结构成员在调用 CreateThread() 之前是否已经实际写入内存?有没有保证它们不会被缓存?例如:

struct bigapple { string color; int count; } apple;
apple.count = 1;
apple.color = "red";
hThread = CreateThread( NULL, 0, myfunction, &apple, 0, NULL );

DWORD WINAPI myfunction( void *param )
{
    struct bigapple *myapple = (struct bigapple *)param;

    // how do I know that apple's struct was actually written to memory before CreateThread?
    cout << "Apple count: " << myapple->count << endl; 
}

今天下午,当我阅读时,我在这个网站上看到了很多 Windows 代码以及其他将非易失性数据传递给线程的代码,并且似乎没有任何内存障碍或其他任何东西。我知道 C++ 或至少旧版本不是“线程感知”的,所以我想知道是否还有其他原因。我的猜测是编译器看到我在调用 CreateThread() 时传递了一个指针 &apple,所以它知道在调用之前写出成员apple

谢谢

4

5 回答 5

5

不。相关的 Win32 线程函数都负责必要的内存屏障。之前的所有写入CreateThread对新线程都是可见的。显然,新创建的线程中的读取不能在调用之前重新排序CreateThread

volatile不会在编译器上添加任何额外有用的约束,只会减慢代码速度。不过,在实践中,与创建新线程的成本相比,这并不明显。

于 2012-09-11T07:16:06.483 回答
2

不,不应该volatile。同时,您指出了有效的问题。缓存的详细操作在 Intel/ARM/etc 论文中有描述。

不过,您可以放心地假设数据被写入。否则会破坏太多东西。几十年的经验告诉我们确实如此。

如果线程调度程序将在同一个内核上启动线程,则缓存状态良好,否则,内核将刷新缓存。否则,什么都行不通。

永远不要volatile用于线程之间的交互。它是关于如何仅在线程内处理数据的指令(使用寄存器副本或始终重新读取等)。

于 2012-09-11T06:36:43.443 回答
1

首先,我认为优化器不能以牺牲正确性为代价来改变顺序。CreateThread() 是一个函数,函数调用的参数binidng 发生调用之前。

其次,volatile对于您想要的目的不是很有帮助。看看这篇文章

于 2012-09-11T05:41:34.127 回答
0

你正在努力解决一个非问题,并且正在创造至少另外两个......

  1. 不要担心给 CreateThread 的参数:如果它们在创建线程时存在,它们会一直存在,直到 CreateThread 返回。而且由于创建它们的线程不会破坏它们,因此它们也可用于其他线程。
  2. 现在的问题变成了它们将被销毁的对象和时间:您使用它们创建它们,new因此它们将一直存在,直到delete调用 a (或直到进程终止:良好的内存泄漏!)
  3. 该进程在其主线程终止时终止(并且所有其他线程也将被操作系统终止!)。您的 main 中没有任何内容可以等待其他线程完成。
  4. 使用低级 API 时要小心,比如CreateThread表单语言,它们有自己的库也与线程接口。C 运行时具有_beginthreadex. 它还为您将错过的 C++ 库调用CreateThread并执行其他初始化任务。如果没有这些初始化,某些 C(和 C++)库函数可能无法正常工作,这也是在终止时正确释放运行时资源所必需的。UnsingCreateThread就像在用于清理malloc的上下文中使用。delete

正确的主线程行为应该是

// create the data
// create the other thread
// // perform othe task
// wait for the oter thread to terminate
// destroy the data

win32 API 文档没有说清楚的是 every HANDLEis waitable,并在释放关联资源时发出信号。要等待其他线程终止,您的主线程只需调用

WaitForSingleObject(hthread,INFINITE);

所以主线程会更合适:

{
    data* pdata = new data;
    HANDLE hthread = (HANDLE)_beginthreadex(0,0,yourprocedure, pdata,0,0);
    WaitForSingleObject(htread,INFINITE);
    delete pdata;
}

甚至

{
    data d;
    HANDLE hthread = (HANDLE)_beginthreadex(0,0,yourprocedure, &d,0,0);
    WaitForSingleObject(htread,INFINITE);
}
于 2012-09-11T06:50:23.277 回答
0

我认为这个问题在另一种情况下是有效的。正如其他人指出的那样,使用结构并且内容是安全的(尽管对数据的访问应该是同步的)。

但是,我认为如果您有一个可以在线程外更改的原子变量(或指向一个的指针),那么这个问题是有效的。在这种情况下,我的意见是在这种情况下应该使用 volatile 。

编辑: 我认为 wiki 页面上的示例是一个很好的解释http://en.wikipedia.org/wiki/Volatile_variable

于 2012-09-11T07:37:02.213 回答