CreateThread 的 threadProc 竞争条件

Posted

技术标签:

【中文标题】CreateThread 的 threadProc 竞争条件【英文标题】:CreateThread's threadProc race condition 【发布时间】:2018-08-16 23:06:33 【问题描述】:

我需要生成 4 个线程,它们基本上做同样的事情,但每个线程都有不同的变量。所以我调用 ::CreateThread 4 次,给出相同的 threadProc 和 'this' 作为参数。现在在 threadProc 中,我需要选择正确的变量来使用。我有一个对象向量,并且在每次 CreateThread 调用之后立即将对象推入其中。

// at this point myVec has say, 2 items
HANDLE hThread = ::CreateThread( NULL, NULL, threadProc, (LPVOID)this, NULL, NULL );
myVecObj.threadHandle = hThread;
myVec.push_back(myVecObj); // myVec.Size = 3 now

DWORD CALLBACK myClass::threadProc(LPVOID lpContext)

     myClass *pMyClass = (myClass *)lpContext;
     int vecCount = pMyClass->myVec.size; // Is this 3??
     char * whatINeed = (char*)pMyClass->myVec[vecCount-1].whatINeed;

我的疑问/问题是 threadProc 的触发速度有多快 - 它能否击败对 myVec.push_back() 的调用?这是我在这里介绍的比赛条件吗?我试图假设当每个 threadProc 开始时(它们在不同的时间开始,而不是一个接一个地开始),我可以安全地获取类向量中的最后一个对象。

【问题讨论】:

使用 _beginthreadex() 和全局范围 f 【参考方案1】:

我需要生成 4 个线程,它们基本上做同样的事情,但每个线程都有不同的变量。所以我调用::CreateThread 4 次,将threadProcthis 作为参数。

现在在threadProc,我需要选择正确的变量来使用。

为什么不向线程传递一个指向它需要操作的实际对象的指针?

我有一个对象向量,我在每次调用 CreateThread 后立即将对象推入其中。

这是错误的处理方式。是的,这是一个竞争条件。不仅因为显而易见的原因 - 线程可能在对象被推送之前开始运行 - 而且因为 any 推送到向量中可能会重新分配向量的内部数组,这对线程来说是非常糟糕的已经获得了指向其向量内数据的指针。数据会在线程背后的内存中移动。

要解决这个问题,您需要:

    首先将所有对象推入向量中,然后启动线程。您可以将指向每个向量元素的指针传递给其各自的线程。但这仅在您在任何线程运行时不再修改向量时才有效,原因如上所述。

    首先以挂起状态启动线程,然后在将所有对象推入向量后恢复它们。这也要求您不再修改向量。这也意味着您必须向每个线程传递一个向量元素的索引,而不是传递一个指向该元素的指针。

    完全摆脱向量(或至少将其更改为保存对象指针而不是实际对象)。使用new 动态分配您的对象,并根据需要将这些指针传递给每个线程(也可以选择传递给向量)。在退出之前让每个线程delete 其对象(并可选择将其从向量中移除,并进行适当的同步)。

我的疑问/问题是threadProc 起火的速度有多快

这完全取决于操作系统调度程序来决定。

它能否击败对myVec.push_back() 的调用?

是的,这是可能的。

这是我在这里介绍的竞争条件吗?

是的。

我试图做出假设

不要做假设!

当每个threadProc 开始时(它们在不同的时间开始,而不是一个接一个),我可以安全地获取类向量中的最后一个对象。

这不是一个安全的假设。

【讨论】:

很好的答案。感谢您的反馈。【参考方案2】:

修改myVec,即myVec.push_back()调用,和在另一个线程中读取对象的大小没有同步。我确实意识到您不使用标准线程但应用 C++11 规则存在数据竞争并且程序具有未定义的行为。

请注意,数据竞争不仅仅是理论上的:您很有可能看到修改发生在读取之后。创建线程可能不会很快,但有些实现实际上并没有创建操作系统级别的线程,而是保留了一个线程池,显然在产生新线程时会使用这些线程池。

在类似的情况下,我听到了一个很好的论点“......但它只发生一百万次!”。假设“百万分之一”的估计是正确的,那么这个特殊问题在 48 核机器上大约每秒会发生 10 次。

【讨论】:

谢谢。我绝对打算在 myVec 上添加同步——但这无助于我遇到的问题。我想我需要找到一种更好的方法来区分不同的 threadProc 调用。

以上是关于CreateThread 的 threadProc 竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

windows API主线程如何等待子线程结束后再继续运行

两个线程可以使用相同的线程过程吗?

线程应当如何正常退出?

C++怎么在主线程中使用子线程的数据? 比如说主线程中有一个数组,如何在子线程中调用这个数组

VC中MFC创建了多线程,界面怎么还会有卡死现象,但程序在正确执行(可以执行出结果)

为啥 CreateThread 不起作用?