gpt4 book ai didi

c++ - 如何在 C++ 中的线程之间传递对静态成员变量的更改

转载 作者:搜寻专家 更新时间:2023-10-31 01:52:29 24 4
gpt4 key购买 nike

我有一个使用静态成员变量作为标志的类。该程序是多线程的,对静态变量值的更改不会在线程之间一致地进行通信。

代码如下:

MyClass.h 文件:

class MyClass 
{
private:
void runLoop();
static bool shutdownRequested;
};

MyClass.cpp 文件:

bool MyClass::shutdownRequested = false;  // static variable definition

void MyClass::runLoop()
{
// much code omitted

if (isShutdownNecessary() && !shutdownRequested)
{
shutdownRequested = true; // Race condition, but that's OK
MyLog::Error("Service shutdown requested");
// more code omitted
}
}

我预计上面显示的日志行可能只出现一次,但由于竞争条件,理论上每个线程可能出现一次。 (在我的情况下,竞争条件是可以接受的。)但是,我看到日志行在每个线程中出现了数十次。我可以这么说,因为 MyLog 类还为每个日志行记录线程 ID、进程 ID 等。

到目前为止,我只在 Windows 发布版本上观察到这个问题。我还没有在 Windows 调试版本或 Linux 版本中观察到它。

由于在多核处理器上的不同内核上运行不同的线程,我可以理解每个线程只看到一次日志行。我很惊讶地看到相同的线程一遍又一遍地执行日志行。

任何人都可以阐明可能导致这种情况发生的特定机制,以及我可以做什么(例如同步)来强制更新要识别的静态变量的值?

最佳答案

一般来说,“比赛正常”从不是真的。数据竞争定义为普通变量的同时写入和读取,在我所知道的每个线程模型(包括 Visual C++、POSIX 线程和 C++11)下都是未定义的行为

就是说,既然您提到您使用的是 Visual C++,那么您可以将共享变量声明为“volatile”。 Microsoft's documentation says :

When the /volatile:ms compiler option is used—by default when architectures other than ARM are targeted—the compiler generates extra code to maintain ordering among references to volatile objects in addition to maintaining ordering to references to other global objects. In particular:

A write to a volatile object (also known as volatile write) has Release semantics; that is, a reference to a global or static object that occurs before a write to a volatile object in the instruction sequence will occur before that volatile write in the compiled binary.

A read of a volatile object (also known as volatile read) has Acquire semantics; that is, a reference to a global or static object that occurs after a read of volatile memory in the instruction sequence will occur after that volatile read in the compiled binary.

This enables volatile objects to be used for memory locks and releases in multithreaded applications.

这至少使行为定义明确。从多个线程都可以记录消息的意义上讲,您仍然存在竞争条件,但这不是“未定义行为”意义上的“数据竞争”。

至于为什么一个线程可能不会“看到它自己的更新”,如果没有同步,一个线程可能会“推测性地存储”到地址以提高性能。也就是说,编译器可能会发出如下代码:

bool tmp = shutdownRequested;
shutdownRequested = true;
if (isShutdownNecessary() && !tmp)
{
MyLog::Error("Service shutdown requested");
// more code omitted
}
else
shutdownRequested = false;

只要编译器能够证明isShutdownNecessary() 不访问shutDownRequested,这对于单线程程序来说就是一个合法的转换。编译器(或 CPU)可能认为这个推测版本更快。但是在多线程的情况下,它可能会导致您看到的行为。反汇编会让您确定...

这种推测性执行在每一代编译器和 CPU 中都变得更加激进,是“数据竞争”非常具体地调用未定义行为的原因之一。如果您的代码有机会活到下周以后,您就是不想去那里。

volatile 声明将阻止 Visual Studio 进行此类转换。但是跨平台解决此问题的唯一方法是使用互斥锁进行适当的锁定(如果这是一个繁忙的循环,则可能是一个条件变量)。这些细节在 C++11 之前的平台之间有所不同。

关于c++ - 如何在 C++ 中的线程之间传递对静态成员变量的更改,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12591803/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com