当另一个线程可能设置共享布尔标志(最多一次)时,可以读取共享布尔标志而不锁定它吗?

问题描述 投票:0回答:4

我希望我的线程能够更优雅地关闭,因此我尝试实现一个简单的信号机制。我不认为我想要一个完全事件驱动的线程,所以我有一个工作人员有一种方法可以使用关键部分优雅地停止它

Monitor
(我相信相当于 C#
lock
):

绘图线程.h

class DrawingThread {
    bool stopRequested;
    Runtime::Monitor CSMonitor;
    CPInfo *pPInfo;
    //More..
}

绘图线程.cpp

void DrawingThread::Run() {
    if (!stopRequested)
        //Time consuming call#1
    if (!stopRequested) {
        CSMonitor.Enter();
        pPInfo = new CPInfo(/**/);
        //Not time consuming but pPInfo must either be null or constructed. 
        CSMonitor.Exit();
    }
    if (!stopRequested) {
        pPInfo->foobar(/**/);//Time consuming and can be signalled
    }
    if (!stopRequested) {
        //One more optional but time consuming call.
    }
}


void DrawingThread::RequestStop() {
    CSMonitor.Enter();
    stopRequested = true;
    if (pPInfo) pPInfo->RequestStop();
    CSMonitor.Exit();
}

我理解(至少在Windows中)

Monitor
/
lock
是最便宜的线程同步原语,但我热衷于避免过度使用。我应该包装这个布尔标志的每次读取吗?它被初始化为 false,并且仅在请求停止时设置一次为 true(如果在任务完成之前请求)。

我的导师建议甚至保护

bool
,因为读/写可能不是原子的。我认为这个一击标志是证明规则的例外?

c++ multithreading locking boolean monitor
4个回答
61
投票

在没有同步的情况下读取可能在不同线程中修改的内容是绝对不行的。需要什么级别的同步取决于您实际阅读的内容。对于原始类型,您应该查看原子读取,例如以

std::atomic<bool>
的形式。

始终需要同步的原因是处理器可能会在缓存行中共享数据。如果没有同步,则没有理由将此值更新为可能在不同线程中更改的值。更糟糕的是,如果没有同步,如果存储在值附近的内容被更改和同步,则可能会写入错误的值。


18
投票

布尔赋值是原子的。这不是问题。

问题在于,由于编译器或 CPU 指令重新排序或数据缓存,线程可能看不到其他线程对变量所做的更改(即读取布尔标志的线程可能会读取缓存值,而不是实际值)更新值)。

解决方案是内存栅栏,它确实是通过 lock 语句隐式添加的,但对于单个变量来说就有点大材小用了。只需将其声明为

std::atomic<bool>


8
投票

我相信答案是“视情况而定”。 如果您使用的是 C++03,标准中没有定义线程,您必须阅读编译器和线程库的说明,尽管这种事情通常被称为“良性竞争”并且通常可以

如果您使用 C++11,良性竞争是未定义的行为。 即使未定义的行为对于基础数据类型没有意义。 问题是编译器可以假设程序没有未定义的行为,并基于此进行优化(另请参阅从那里链接的第 1 部分和第 2 部分)。 例如,您的编译器可能决定读取该标志一次并缓存该值,因为在没有某种互斥锁或内存屏障的情况下在另一个线程中写入变量是未定义的行为。

当然,很可能你的编译器承诺不进行这种优化。 你需要看看。

最简单的解决方案是在 C++11 中使用

std::atomic<bool>
,或者在其他地方使用 Hans Boehm 的atomic_ops


1
投票

不,您必须保护每次访问,因为现代编译器和 CPU 会在不考虑多线程任务的情况下对代码进行重新排序。来自不同线程的读取访问可能有效,但不一定有效。

最新问题
© www.soinside.com 2019 - 2025. All rights reserved.