同步线程 - InterlockedExchange

时间:2016-01-22 21:26:52

标签: c++ multithreading winapi

我想检查线程是否正常工作。如果线程正在工作,我将等待一个事件,直到线程停止工作。线程将在结束时设置的事件。

要检查线程是否正常工作,我声明了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值是原子方式或者挥发性强制转换是否足够,那么我真的不清楚。

3 个答案:

答案 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_weakcompare_exchange_strong个函数。这些用于将对象的值设置为某个NewValue,前提是它的当前值是TestValue并指示此尝试的状态(是否进行了更改)。这些功能的好处在于,这样做可以保证,如果两个线程试图执行此操作,则只有一个会成功。当您需要根据操作结果采取某些操作时,这非常有用。