C++11 条件变量语义

C++11 condition variable semantics

我试图理解 std::condition_variable. I thought I had a decent understanding of the C++11 concurrency model (atomics, memory ordering, the corresponding guarantees and formal relations) 的语义,但是关于如何正确使用条件变量的描述似乎与我的理解相矛盾。

TL;DR

reference 说:

The thread that intends to modify the variable has to

  1. acquire a std::mutex (typically via std::lock_guard)
  2. perform the modification while the lock is held
  3. execute notify_one or notify_all on the std::condition_variable (the lock does not need to be held for notification)

Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.

我明白为什么必须在释放 互斥体之前进行修改,但是上面的内容似乎很清楚,必须同时持有 互斥量,即它不能在获得它之前。我没看错吗?

更详细

如果我上面的解读是正确的,那为什么会这样呢?考虑我们在关键部分之前进行修改(通过正确使用原子和锁确保没有竞争条件)。例如

std::atomic<bool> dummy;
std::mutex mtx;
std::condition_variable cv;

void thread1() {
    //...
    // Modify some program data, possibly in many places, over a long period of time
    dummy.store(true, std::memory_order_relaxed); // for simplicity
    //...
    mtx.lock(); mtx.unlock();
    cv.notify_one();
    //...
}

void thread2() {
    // ...
    { std::unique_lock<std::mutex> ul(mtx);
        cv.wait(ul, []() -> bool {
            // A complex condition, possibly involving data from many places
            return dummy.load(std::memory_order_relaxed); // for simplicity
        });
    }
    // ...
}

我的理解是 cv.wait() 在继续之前锁定 mtx(检查条件并执行程序的其余部分)。此外,std::mutex::lock() 算作 acquire 操作,std::mutex::unlock() 算作 release 操作。这是否不意味着线程 1 中的 unlock() 与线程 2 中的 lock() 同步,因此在 unlock() 之前在线程 1 中执行的所有原子甚至非原子存储线程 2 醒来时可见吗?

Formally:  store --sequenced-before--> unlock() --synchronizes-with--> lock() --sequenced-before--> load
...and so: store --happens-before--> load

非常感谢您的回答!

[注意:我发现很奇怪,经过广泛的谷歌搜索后我还没有找到答案;很抱歉,如果重复...]

考虑锁定线程 1 中的互斥量之前的时间和 condition_variable 首次解锁线程 2 中的互斥量之前的时间。

thread1 做

  • 修改大量程序数据
  • dummy.store(true, std::memory_order_relaxed)

thread2 做

  • 锁定互斥体
  • dummy.load(std::memory_order_relaxed)(等待前检查谓词)

彼此之间没有先后顺序。如果 thread2 在此检查中看到 dummy 的真值并继续,则无法保证任何数据修改对 thread2 可见。 thread2 将继续,已正确看到 dummy 的值但没有正确看到修改。

你说"ensuring no race conditions, via correct use of atomics and locks"这是很开放的。宽松的原子是正确的,修改不一定在 thread2 中可见。但是,围绕这些其他数据修改的假设额外同步可以保证可见性。

换句话说,在存储和加载之间应该有一些发布-获取顺序。

这类似于: