我可以在多线程C++中安全地使用int吗

Can I use int in multithreaded C++ safely?

本文关键字:int 安全 多线程 C++ 我可以      更新时间:2023-10-16

我有以下代码。有什么东西可以使它不穿线吗?

class runner
{
public:
    volatile int exitFlag;
    // construct in thread A
    runner()
    {
        exitFlag = 0;
    }
    // run it in thread B
    void threadFunc()
    {
        // does not matter when the change is getting here
        while (exitFlag == 0)
        {
            // ... do stuff (not using exitFlag)
        }
    }
    // call it from thread A
    void signalThread()
    {
        exitFlag = 1; // only change one bit;
    }
};

我创建了一个runner对象,并在另一个线程中启动它的threadFunc,稍后从第一个线程对它调用signalThread。有人告诉我,在不同的线程中访问同一个变量(至少其中一个是写的)可能会导致读取垃圾值。但显然,在上面的代码中(因为只更改了一位),这并不重要。读/写的顺序也无关紧要。

不要使用volatile在线程之间共享变量。它没有提供原子性或同步性的适当保证。原子性的缺乏不太可能导致这里的特定问题,尽管从形式上讲,它会导致未定义的行为。缺乏同步意味着线程完全有可能永远看不到变化,并永远继续运行。

在C++11或更高版本中,请使用std::atomic<int>。该语言的早期版本没有对线程的标准支持,因此您必须使用编译器提供的任何非标准工具。

数据成员不受保护(例如由互斥体保护)这一简单事实使代码不具有线程安全性。几个线程可以访问runner的同一实例(例如,通过指针或引用),并且可以同时访问exitflag,因为没有同步机制。

为了回答您评论中的问题:由于int被定义为CPU的"本机"类型,我相信给定的读写操作是一条机器代码指令,不会被另一个线程中断。不过,我不知道C/C++是否能保证这种行为。因此,在您的场景中,它可能会起作用,尽管在一般情况下您需要同步。

为了澄清所说的话,尽管有点被掩盖了,但这似乎是因为你没有说你理解:

当一个CPU上的值发生更改时,它大多/通常只会写入该CPU的缓存。其他CPU不会看到它——如果不应用同步技术,其他线程就不应该使用完全相同的内存!

这个改变后的记忆单词最终可能会被刷新到主记忆中,但这需要多长时间是完全不可预测的。此外,如果其他CPU认为他们在自己的缓存中有相同的内存字,他们可能不会去寻找另一个副本,直到他们丢弃了那块内存,然后再次获取它。

互斥使用"内存屏障",这基本上是CPU和子系统中的机制,允许线程说"我希望看到所有数据的TRUE值在这个互斥体之前和影响下都发生了变化。因此,当一个线程获取互斥体的锁,修改内存并释放互斥体时,任何其他获取该互斥体的线程都将保证看到所有内存在该互斥体保护下发生了变化"。

您必须在多线程系统中使用互斥或其他同步对象,尤其是在这个多核CPU时代。回到单核心时代,你会逃脱你的提议;当时唯一的风险就是破坏原子性。在单核机器上,即使是一个简单的机器字也可能遭受破坏的读/修改/写周期,更不用说多核了。。。

总结评论(主要来自Mike Seymour)

  • 可用时应使用std::atomic
  • 如果没有,那么互斥和内存屏障就进入了作用域

发布的代码包含未定义的行为(由c++标准规定)但可能出现的唯一具体问题是线程B无法在合理的时间内得到更改,因为它们被卡在cpu中。