我想检查线程是否正常工作。如果线程正在工作,我将等待一个事件,直到线程停止工作。线程将在结束时设置的事件。
要检查线程是否正常工作,我声明了volatile bool
变量。如果线程正在运行,bool
变量将为true
,否则为false
。在线程结束时,bool变量将设置为false
。
使用volatile bool
变量是否足够,还是必须使用原子函数?
InterlockedExchange
方法,我不明白我需要这个功能的用例。
更新
我看到没有我的代码,不清楚是否一个易变的bool变量就足够了。我写了一个测试类来显示我的问题。
class Testclass
{
public:
Testclass(void);
~Testclass(void);
void doThreadedWork();
void Work();
void StartWork();
void WaitUntilFinish();
private:
HANDLE hHasWork;
HANDLE hAbort;
HANDLE hFinished;
volatile bool m_bWorking;
};
//。CPP
#include "stdafx.h"
#include "Testclass.h"
CRITICAL_SECTION cs;
DWORD WINAPI myThread(LPVOID lpParameter)
{
Testclass* pTestclass = (Testclass*) lpParameter;
pTestclass->doThreadedWork();
return 0;
}
Testclass::Testclass(void)
{
InitializeCriticalSection(&cs);
DWORD myThreadID;
HANDLE myHandle = CreateThread(0, 0, myThread, this, 0, &myThreadID);
m_bWorking = false;
hHasWork = CreateEvent(NULL,TRUE,FALSE,NULL);
hAbort = CreateEvent(NULL,TRUE,FALSE,NULL);
hFinished = CreateEvent(NULL,FALSE,FALSE,NULL);
}
Testclass::~Testclass(void)
{
DeleteCriticalSection(&cs);
CloseHandle(hHasWork);
CloseHandle(hAbort);
CloseHandle(hFinished);
}
void Testclass::Work()
{
// do some work
m_bWorking = false;
SetEvent(hFinished);
}
void Testclass::StartWork()
{
EnterCriticalSection(&cs);
m_bWorking = true;
ResetEvent(hFinished);
SetEvent(hHasWork);
LeaveCriticalSection(&cs);
}
void Testclass::doThreadedWork()
{
HANDLE hEvents[2];
hEvents[0] = hHasWork;
hEvents[1] = hAbort;
while(true)
{
DWORD dwEvent = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE);
if(WAIT_OBJECT_0 == dwEvent)
{
Work();
}
else
{
break;
}
}
}
void Testclass::WaitUntilFinish()
{
EnterCriticalSection(&cs);
if(!m_bWorking)
{
// if the thread is not working, do not wait and return
LeaveCriticalSection(&cs);
return;
}
WaitForSingleObject(hFinished,INFINITE);
LeaveCriticalSection(&cs);
}
对我来说,如果m_bWorking值是原子方式或者挥发性强制转换是否足够,那么我真的不清楚。
答案 0 :(得分:1)
出于某种原因,Interlocked API不包含" InterlockedGet"或" InterlockedSet"功能。这是一个奇怪的遗漏,典型的解决方法是通过挥发性。
您可以在Windows上使用以下代码:
#include <intrin.h>
__inline int InterlockedIncrement(int *j)
{ // This is VS-specific
return _InterlockedIncrement((volatile LONG *) j);
}
__inline int InterlockedDecrement(int *j)
{ // This is VS-specific
return _InterlockedDecrement((volatile LONG *) j);
}
__inline static void InterlockedSet(int *val, int newval)
{
*((volatile int *)val) = newval;
}
__inline static int InterlockedGet(int *val)
{
return *((volatile int *)val);
}
是的,它很难看。但如果你不使用C ++ 11,这是解决这个问题的最佳方法。如果您使用的是C ++ 11,请改用std::atomic
。
请注意,这是特定于Windows的代码,不应在其他平台上使用。
答案 1 :(得分:1)
您的问题有很多背景可供选择。我们不知道你正在使用什么样的工具链,所以我将把它作为一个winapi问题来回答。我进一步假设你有这样的想法:
volatile bool flag = false;
DWORD WINAPI WorkFn(void*) {
flag = true;
// work here
....
// done.
flag = false;
return 0;
}
int main() {
HANDLE th = CreateThread(...., &WorkFn, NULL, ..);
// wait for start of work.
while (!flag) {
// ?? # 1
}
// Seems thread is busy now. Time to wait for it to finish.
while (flag) {
// ?? # 2
}
}
这里有很多不妥之处。对于初学者来说,volatile
在这里做的很少。当flag = true
发生时,它最终将对另一个线程可见,因为它由全局变量支持。这是因为它至少会使它进入缓存,并且缓存有办法告诉其他处理器给定的行(这是一个地址范围)是脏的。它不会进入缓存的唯一方法是,如果编译器进行超级疯狂优化,其中flag
作为寄存器保留在cpu中。这可能实际发生,但在这个特定的代码示例中没有。
因此volatile告诉编译器永远不要将变量保存为寄存器。就是这样,每当你看到一个易变的变量,你就可以把它翻译为&#34;永远不会注册这个变量&#34;。它在这里使用基本上是一个偏执的举动。
如果这个代码是您的想法,那么这个循环遍历标志模式称为Spinlock
,而这个代码真的很差。在用户模式程序中几乎不是正确的事情。
在我们采用更好的方法之前,让我解决你的联锁问题。人们通常的意思是这种模式
volatile long flag = 0;
DWORD WINAPI WorkFn(void*) {
InterlockedExchange(&flag, 1);
....
}
int main() {
...
while (InterlockedCompareExchange(&flag, 1, 1) = 0L) {
YieldProcessor();
}
...
}
假设...
表示与以前类似的代码。 InterlockedExchange()
正在做的是强制写入内存以确定性的方式发生,&#34;现在广播变化&#34;,一种方式和在同一时间内读取它的典型方式。绕过缓存&#34;方式是通过InterlockedCompareExchange()
。
它们的一个问题是它们会在系统总线上产生更多流量。也就是说,现在总线用于在系统上的cpu中广播缓存同步数据包。
std::atomic<bool> flag
将是现代的C ++ 11方法,但仍然不是你真正想做的事情。
我在那里添加了YieldProcessor()
来指出真正的问题。等待内存地址更改时,您正在使用cpu资源,这些资源可以更好地用于其他地方,例如在实际工作中(!!)。如果您实际产生处理器,则操作系统至少有可能将其提供给WorkFn
,但在多核机器中,它将很快返回轮询变量。在现代机器中,您将每秒检查flag
数百万次,产量可能达到每秒200000次。无论如何都浪费了。
您要做的是利用Windows进行零成本等待,或者至少以低成本进行操作:
DWORD WINAPI WorkFn(void*) {
// work here
....
return 0;
}
int main() {
HANDLE th = CreateThread(...., &WorkFn, NULL, ..);
WaitForSingleObject(th, INFINITE);
// work is done!
CloseHandle(th);
}
当您从工作线程返回时,线程句柄会发出信号并等待它。虽然卡在WaitForSingleObject
中,但您不会消耗任何cpu周期。如果你想在等待时在main()函数中进行定期活动,可以用1000替换INFINITE
,这将每秒释放主线程。在这种情况下,您需要检查WaitForSingleObject
的返回值,以告知线程完成情况下的超时。
如果您确实需要知道何时开始工作,则需要一个额外的可等待对象,例如,通过CreateEvent()
获得的Windows事件,并且可以使用相同的WaitForSingleObject
等待。 / p>
更新[1/23/2016]
现在我们可以看到你想到的代码,你不需要原子,volatile
就可以了。 m_bWorking
无论如何true
都受cs互斥锁的保护。
如果我建议,您可以使用TryEnterCriticalSection
和cs完成同样的操作而不需要m_bWorking
:
void Testclass::Work()
{
EnterCriticalSection(&cs);
// do some work
LeaveCriticalSection(&cs);
SetEvent(hFinished); // could be removed as well
}
void Testclass::StartWork()
{
ResetEvent(hFinished); // could be removed.
SetEvent(hHasWork);
}
void Testclass::WaitUntilFinish()
{
if (TryEnterCriticalSection(&cs)) {
// Not busy now.
LeaveCriticalSection(&cs);
return;
} else {
// busy doing work. If we use EnterCriticalSection(&cs)
// here we can even eliminate hFinished from the code.
}
...
}
答案 2 :(得分:0)
不,挥发性bool是不够的。你需要一个原子bool,正如你所怀疑的那样。否则,您可能永远不会看到您的bool更新。
C ++中也没有InterlockedExchange
(问题的标签),但C ++ 11中有compare_exchange_weak
和compare_exchange_strong
个函数。这些用于将对象的值设置为某个NewValue,前提是它的当前值是TestValue并指示此尝试的状态(是否进行了更改)。这些功能的好处在于,这样做可以保证,如果两个线程试图执行此操作,则只有一个会成功。当您需要根据操作结果采取某些操作时,这非常有用。