c++ - 同步线程 - InterlockedExchange

标签 c++ multithreading winapi

我喜欢检查线程是否正在工作。如果线程正在工作,我将等待事件,直到线程停止工作。线程将在最后设置的事件。

为了检查线程是否正在工作,我声明了一个 volatile bool 变量。如果线程正在运行,则 bool 变量将为 true,否则为 false。在线程结束时,bool 变量将被设置为 false

使用 volatile bool 变量是否足够,还是必须使用原子函数?

顺便说一句:请有人向我解释一下 InterlockedExchange 方法,我不明白我需要此功能的用例。

更新

我发现如果没有我的代码,就不清楚 volatile 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 值是否以原子方式存在,或者 volatile 转换是否足够。

最佳答案

您的问题涉及很多背景知识。例如,我们不知道您正在使用什么工具链,因此我将作为 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发生这种情况时,它最终将对其他线程可见,因为它由全局变量支持。之所以如此,是因为它至少会进入高速缓存,并且高速缓存有方法告诉其他处理器给定的行(这是一个地址范围)是脏的。它不会进入缓存的唯一方法是,如果编译器进行了 super 疯狂的优化,其中 flag作为寄存器保留在CPU中。这实际上可能发生,但在这个特定的代码示例中不会发生。

所以 volatile 告诉编译器永远不要将变量保留为寄存器。就是这样,每次你看到 volatile 变量时,你都可以将其翻译为“永远不要注册这个变量”。它在这里的使用基本上只是一种偏执的举动。

如果这段代码是您想要的,那么这种对标志模式的循环称为 Spinlock而这个真的很差。在用户模式程序中这几乎从来都不是正确的做法。

在我们讨论更好的方法之前,让我先解决您的互锁问题。人们通常指的是这种模式

volatile long flag = 0;

DWORD WINAPI WorkFn(void*) {
  InterlockedExchange(&flag, 1);
  ....
}

int main() {
 ...
  while (InterlockedCompareExchange(&flag, 1, 1) = 0L) {
    YieldProcessor();
  }
  ...
}

假设...表示与之前类似的代码。什么是InterlockedExchange()正在做的是强制写入内存以一种确定性的方式发生,“立即广播更改”,以相同的“绕过缓存”方式读取它的典型方法是通过 InterlockedCompareExchange()

它们的一个问题是它们在系统总线上产生更多的流量。也就是说,总线现在用于在系统上的 CPU 之间广播缓存同步数据包。

std::atomic<bool> flag将是现代的 C++11 方法来执行相同的操作,但仍然不是您真正想要做的。

我添加了YieldProcessor()调用那里指出真正的问题。当您等待内存地址更改时,您正在使用CPU资源,这些资源最好在其他地方使用,例如在实际工作中(!!)。如果您确实放弃了处理器,那么操作系统至少有机会将其交给 WorkFn ,但在多核机器中,它将很快返回轮询变量。在现代机器中,您将检查此 flag每秒数百万次,以产量计算,大概每秒20万次。无论哪种方式都是可怕的浪费。

您在这里想要做的是利用 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() 函数中执行定期事件,您可以替换 INFINITE为1000,每秒都会释放主线程。在这种情况下,您需要检查 WaitForSingleObject 的返回值告知线程完成情况的超时。

如果您需要实际知道工作何时开始,则需要一个额外的可等待对象,例如通过 CreateEvent() 获取的 Windows 事件并且可以使用相同的 WaitForSingleObject 等待.

更新 [2016 年 1 月 23 日]

现在我们可以看到您想要的代码,您不需要原子,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.
    }
  ...
}

关于c++ - 同步线程 - InterlockedExchange,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34956224/

相关文章:

C++ 结构排序

java - 使用 ScheduledThreadPoolExecutor 允许核心线程超时

c# - 锁定字段或局部变量?

c++ - 构造函数中对 (class)(type) 的调用不匹配

c++ - 这个自动模板推导指南正确吗?

c++ - 从C++中的2个不同线程访问属于同一对象的不同数据成员

c++ - 如何从 Windows 应用程序检测禁用的网络接口(interface)连接?

windows - 在不同进程之间共享 HDC

delphi - 我可以在 Delphi 中即时编译代码并执行它吗?

c++ - 我们可以将数组变量添加到整数吗?