需要对条件变量寻求的互斥保护(原子)赋值吗?

Posted

技术标签:

【中文标题】需要对条件变量寻求的互斥保护(原子)赋值吗?【英文标题】:Need to mutex-protect (atomic) assignment sought by condition variable? 【发布时间】:2012-07-18 17:46:49 【问题描述】:

我了解如何使用条件变量(这个构造的糟糕名称 IMO,因为 cv 对象既不是变量也不是条件)。所以我有一对线程,canonically 使用 Boost.Thread 设置为:

bool awake = false;
boost::mutex sync;
boost::condition_variable cv;
void thread1()

    boost::unique_lock<boost::mutex> lock1(sync);
    while (!awake)
        cv.wait(lock1);
    lock1.unlock();    // this line actually not canonical, but why not?
    // proceed...

void thread2()

    //...
    boost::unique_lock<boost::mutex> lock2;
    awake = true;
    lock2.unlock();
    cv.notify_all();

我的问题是:thread2 真的需要保护对awake 的分配吗?在我看来,notify_all() 电话应该就足够了。如果被操作和检查的数据不仅仅是一个简单的“可以继续”标志,我会看到互斥锁中的值,但在这里看起来有点过头了。

第二个问题是代码片段中提出的问题:为什么 Boost 文档没有显示线程 1 中的锁在“处理数据”步骤之前被解锁?

编辑:也许我的问题真的是:有没有比 CV 更简洁的结构来实现这种等待?

【问题讨论】:

【参考方案1】:

thread2 真的需要保护分配以唤醒吗?

是的。从一个线程修改对象并在不同步的情况下从另一个线程访问它会产生未定义的行为。哪怕只是一个bool

例如,在某些多处理器系统上,写入可能只影响本地内存;如果没有显式的同步操作,其他线程可能永远看不到更改。

为什么 Boost 文档没有显示线程 1 中的锁在“处理数据”步骤之前被解锁?

如果您在清除标志之前解锁了互斥锁,那么您可能会错过另一个信号。

有没有比 CV 更简洁的结构来实现这种等待?

在 Boost 和标准 C++ 库中,没有;条件变量足够灵活,可以处理任意共享状态,并且对于这种简单的情况不会特别复杂,因此没有特别需要更简单的东西。

更一般地说,您可以使用信号量或管道在线程之间发送简单的信号。

【讨论】:

这是记忆同步的东西,让我一直绊倒。关于您的最后一点:信号量(按该名称)不是 Boost 或 C++11 线程的一部分; Boost.InterProcess 有一个。目前还不清楚它会如何更简单。同样,我不会将管道用于单个进程。 @MikeC:没错,正如我的回答所说,Boost 或 C++ 线程库中没有信号量。从概念上讲,信号量或管道可能被认为稍微干净一些,因为它是单个对象并且不涉及任何外部共享状态。就个人而言,我更喜欢通过消息队列等进行更高级别的同步,避免显式共享状态,但遗憾的是,这并没有成为这个版本的标准。【参考方案2】:

形式上,你肯定需要两个线程中的锁:如果有线程 修改一个对象,并且多个线程访问它,然后 all 访问必须同步。

在实践中,您可能会在没有锁的情况下侥幸逃脱;它是 几乎可以肯定notify_all 将发出必要的围栏或 membar 指令以确保内存正确同步。 但是为什么要冒险呢?

至于unlock 的缺失,这就是作用域的全部意义所在 锁定模式:unlock 在对象的析构函数中,所以 即使异常通过,互斥锁也将被解锁。

【讨论】:

好吧,显然,如果后续处理要执行额外的操作,这将涉及在同一个互斥体上进一步同步,则必须使用解锁。实际上,我不太喜欢范围锁定,因为这个原因以及它鼓励的丑陋的内联块编码;我更喜欢 Java 语法。 @MikeC:如果你做任何可能引发异常的事情,范围锁定是必不可少的,除非你使用更丑陋的 try/catch/rethrow 块——这就是我不喜欢 Java 语法的原因。 @MikeSeymour:我们必须同意不同意非关键字绑定的开闭大括号结构与带有关键字如 try 和 catch 的相对丑陋。为了引入范围而缩进简直是骇人听闻。 :) 作为替代方案,我也喜欢 Python 的 with @MikeC 您显然会将这个同步协议放在它自己的函数中,在做其他事情之前调用它。良好的功能分解需要它,独立于释放锁的问题。至于Java语法...锁定,在Java中,是语言的一部分,它的实现方式意味着一个函数不能返回一个锁定的对象。一个非常重要和有用的成语。 @MikeC 至于在一般情况下使用 try/finally 而不是析构函数......析构函数将责任放在需要它的对象的作者手中; try/catch 将其交到客户手中。 Java 代码中最常见的错误之一是客户端忘记了需要“销毁”的对象(例如需要关闭的流)的 try/finally。

以上是关于需要对条件变量寻求的互斥保护(原子)赋值吗?的主要内容,如果未能解决你的问题,请参考以下文章

线程间的通信方式以及线程与进程的区别

C++11——线程库,互斥量,原子性库,条件变量

自旋锁互斥锁信号量原子操作条件变量在不同开源框架的应用

四十Linux 线程——线程同步之条件变量

Linux中 条件变量为啥要用互斥锁来保护?

(转载)Linux 多线程条件变量同步