此代码中是否存在数据竞争?

时间:2013-05-11 00:55:41

标签: c++ multithreading c++11 concurrency valgrind

this page中,编写此示例代码以说明如何使用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错了?

3 个答案:

答案 0 :(得分:4)

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

您的代码中没有数据竞争。由于条件变量工作方式的微妙之处,Helgrind发出此警告。在the "hints" section of the Helgrind manual中有一些关于它的讨论。简而言之:在数据竞争检测之前,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手册的提示部分中“使用信号量而不是condition_variables”的建议是可怕的建议。信号量比工具和人类的条件变量更难检查正确性。信号量是“过于笼统”的,因为有各种各样的不变量是你无法依赖的。与信号量“锁定”的相同线程不必是“解锁”的线程。在非二进制信号量上“等待”的两个线程可能会或可能不会发生之前的关系。因此,如果您试图推理(或自动检测)死锁或数据竞争条件,信号量几乎无用。

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

答案 1 :(得分:2)

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

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

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

答案 2 :(得分:0)

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

您已在unlock循环中颠倒了lockwhile的顺序,这样您就可以在锁定之前尝试解锁互斥锁,这似乎与你看到的valgrind信息。