2

此页面中,编写此示例代码是为了解释如何使用notify_one

#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>

std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;

void waits()
{
    std::unique_lock<std::mutex> lk(cv_m);
    std::cout << "Waiting... \n";
    cv.wait(lk, []{return i == 1;});
    std::cout << "...finished waiting. i == 1\n";
    done = true;
}

void signals()
{
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Notifying...\n";
    cv.notify_one();

    std::unique_lock<std::mutex> lk(cv_m);
    i = 1;
    while (!done) {
        lk.unlock();
        std::this_thread::sleep_for(std::chrono::seconds(1));
        lk.lock();
        std::cerr << "Notifying again...\n";
        cv.notify_one();
    }
}

int main()
{
    std::thread t1(waits), t2(signals);
    t1.join(); t2.join();
}

然而,valgrind(实际上是 helgrind)抱怨说:

可能是竞争条件:条件变量 0x605420 已发出信号,但相关联的互斥锁 0x605460 未被信号线程锁定。

如果第二个线程在第一个线程之前运行并在其他线程之前到达cv.notify_one();,它将向其他线程发出信号,而不会持有任何锁。

我实际上正在学习如何使用这些condition variables并试图了解谁应该锁定/解锁与它们关联的互斥锁。所以我的问题是:这段代码做对了吗?或者是 helgrind 错了?

4

3 回答 3

4

[广告的真相:直到最近,我还是与 Helgrind/Valgrind “竞争”的商业数据竞争和内存错误检测器的架构师。]

您的代码中没有数据竞争。Helgrind 发出此警告是因为条件变量工作方式的微妙之处。Helgrind 手册的“提示”部分对此进行了一些讨论。简而言之:Helgrind 正在做发生在数据竞争检测之前的事情。它通过观察您的代码调用 pthread_mutex_lock/unlock 和 pthread_cond_wait/signal 的顺序(这些是实现 C++11 原语的 C 原语)得出“发生前”关系。

如果您遵循这样的原则,即您的cv.notify_one()调用始终受到围绕相应cv.wait()调用的同一个互斥锁的保护,那么 Helgrind 知道互斥锁将强制执行正确的先发生关系,因此一切都会好起来的。

在您的情况下, Helgrind 在您获得锁定之前抱怨cv.notify_one()顶部的初始(无偿)调用。它知道这是一种可能会混淆它的情况(尽管真正的混淆是它可能稍后会报告误报,所以这里的警告信息有点误导。)signals()cv_m

请注意Helgrind 手册提示部分中“使用信号量而不是条件变量”的建议是可怕的建议。对于工具和人类来说,信号量比条件变量更难检查正确性。从某种意义上说,信号量“太笼统”了,因为您不能依赖各种不变量。用信号量“锁定”的同一线程不必是“解锁”的线程。在非二进制信号量上“等待”的两个线程可能有也可能没有发生前的关系。因此,如果您试图推理(或自动检测)死锁或数据竞争条件,信号量几乎毫无用处。

更好的建议是使用条件变量来发出信号/等待,但要确保遵循这样的原则,即对特定条件变量的所有调用都发生在受同一互斥锁保护的关键部分内。

于 2013-05-11T03:39:06.183 回答
2

在你的情况下,没有问题。

通常,如果有第三个线程可能正在使用相同的互斥锁,或者等待线程可能在完成运行时破坏条件变量,则可能会出现问题。

在发出信号时锁定互斥锁更安全,以确保信号代码在唤醒代码之前完全运行。

于 2013-05-11T01:20:11.193 回答
0

编辑:事实证明这不是真的,但它被保留在这里,因为@dauphic 解释为什么它在评论中不正确是有帮助的。

您已经颠倒了循环的顺序unlock和循环,这样您就试图在锁定互斥锁之前对其进行解锁,这似乎与您看到的 valgrind 消息一致lockwhile

于 2013-05-11T01:11:56.940 回答