首页 文章

如何避免与Helgrind的误报?

提问于
浏览
6

我的线程同步“风格”似乎正在抛出helgrind . 这是一个简单的程序,可以重现问题:

#include <thread>
#include <atomic>
#include <iostream>

int main()
{
    std::atomic<bool> isReady(false);

    int i = 1;

    std::thread t([&isReady, &i]()
    {
        i = 2;
        isReady = true;
    });

    while (!isReady)
        std::this_thread::yield();

    i = 3;

    t.join();

    std::cout << i;

    return 0;
}

据我所知,上面是一个完美的程序 . 但是,当我使用以下命令运行helgrind时,我收到错误:

valgrind --tool=helgrind ./a.out

这个输出是:

==6247== Helgrind, a thread error detector
==6247== Copyright (C) 2007-2015, and GNU GPL'd, by OpenWorks LLP et al.
==6247== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==6247== Command: ./a.out
==6247==
==6247== ---Thread-Announcement------------------------------------------
==6247==
==6247== Thread #1 is the program's root thread
==6247==
==6247== ---Thread-Announcement------------------------------------------
==6247==
==6247== Thread #2 was created
==6247==    at 0x56FBB1E: clone (clone.S:74)
==6247==    by 0x4E46189: create_thread (createthread.c:102)
==6247==    by 0x4E47EC3: pthread_create@@GLIBC_2.2.5 (pthread_create.c:679)
==6247==    by 0x4C34BB7: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
==6247==    by 0x5115DC2: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==6247==    by 0x4010EF: std::thread::thread<main::{lambda()#1}>(main::{lambda()#1}&&) (in /home/arman/a.out)
==6247==    by 0x400F93: main (in /home/arman/a.out)
==6247==
==6247== ----------------------------------------------------------------
==6247==
==6247== Possible data race during read of size 1 at 0xFFF00035B by thread #1
==6247== Locks held: none
==6247==    at 0x4022C3: std::atomic<bool>::operator bool() const (in /home/arman/a.out)
==6247==    by 0x400F9F: main (in /home/arman/a.out)
==6247==
==6247== This conflicts with a previous write of size 1 by thread #2
==6247== Locks held: none
==6247==    at 0x40233D: std::__atomic_base<bool>::operator=(bool) (in /home/arman/a.out)
==6247==    by 0x40228E: std::atomic<bool>::operator=(bool) (in /home/arman/a.out)
==6247==    by 0x400F4A: main::{lambda()#1}::operator()() const (in /home/arman/a.out)
==6247==    by 0x40204D: void std::_Bind_simple<main::{lambda()#1} ()>::_M_invoke<>(std::_Index_tuple<>) (in /home/arman/a.out)
==6247==    by 0x401FA3: std::_Bind_simple<main::{lambda()#1} ()>::operator()() (in /home/arman/a.out)
==6247==    by 0x401F33: std::thread::_Impl<std::_Bind_simple<main::{lambda()#1} ()> >::_M_run() (in /home/arman/a.out)
==6247==    by 0x5115C7F: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==6247==    by 0x4C34DB6: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
==6247==  Address 0xfff00035b is on thread #1's stack
==6247==  in frame #1, created by main (???:)
==6247==
==6247== ----------------------------------------------------------------
==6247==
==6247== Possible data race during write of size 4 at 0xFFF00035C by thread #1
==6247== Locks held: none
==6247==    at 0x400FAE: main (in /home/arman/a.out)
==6247==
==6247== This conflicts with a previous write of size 4 by thread #2
==6247== Locks held: none
==6247==    at 0x400F35: main::{lambda()#1}::operator()() const (in /home/arman/a.out)
==6247==    by 0x40204D: void std::_Bind_simple<main::{lambda()#1} ()>::_M_invoke<>(std::_Index_tuple<>) (in /home/arman/a.out)
==6247==    by 0x401FA3: std::_Bind_simple<main::{lambda()#1} ()>::operator()() (in /home/arman/a.out)
==6247==    by 0x401F33: std::thread::_Impl<std::_Bind_simple<main::{lambda()#1} ()> >::_M_run() (in /home/arman/a.out)
==6247==    by 0x5115C7F: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==6247==    by 0x4C34DB6: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
==6247==    by 0x4E476F9: start_thread (pthread_create.c:333)
==6247==    by 0x56FBB5C: clone (clone.S:109)
==6247==  Address 0xfff00035c is on thread #1's stack
==6247==  in frame #0, created by main (???:)
==6247==
3==6247==
==6247== For counts of detected and suppressed errors, rerun with: -v
==6247== Use --history-level=approx or =none to gain increased speed, at
==6247== the cost of reduced accuracy of conflicting-access information
==6247== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

Helgrind似乎正在把我的while循环作为一种竞争条件 . 我怎么形成这个程序以避免helgrind抛出误报?

2 回答

  • 1

    问题是Helgrind并不了解GCC的原子内置,所以没有意识到它们是无竞争的并且对程序进行了排序 .

    有一些方法可以注释你的代码以帮助Helgrind,请参阅http://valgrind.org/docs/manual/hg-manual.html#hg-manual.effective-use(但我不知道如何在这里使用它们,我已经尝试了sbabbi显示的内容,它只解决了部分问题) .

    无论如何,我会避免在繁忙的循环中屈服,这是一种糟糕的同步形式 . 可以使用如下的条件变量来完成:

    #include <thread>
    #include <atomic>
    #include <iostream>
    #include <condition_variable>
    
    int main()
    {
        bool isReady(false);
        std::mutex mx;
        std::condition_variable cv;
    
        int i = 1;
    
        std::thread t([&isReady, &i, &mx, &cv]()
        {
            i = 2;
            std::unique_lock<std::mutex> lock(mx);
            isReady = true;
            cv.notify_one();
        });
    
        {
            std::unique_lock<std::mutex> lock(mx);
            cv.wait(lock, [&] { return isReady; });
        }
    
        i = 3;
    
        t.join();
    
        std::cout << i;
    
        return 0;
    }
    
  • 7

    Valgrind无法知道 while (!isReady) 循环(以及存储和加载上的隐式 memory_order_releasememory_order_consume 标志),意味着 i = 2 语句是 i = 3 之前的依赖顺序 .

    您必须使用valgrind ANNOTATE_HAPPENS_BEFOREANNOTATE_HAPPENS_AFTER 宏显式声明此不变量:

    #include <valgrind/drd.h>
    
    #include <thread>
    #include <atomic>
    #include <iostream>
    
    int main()
    {
        std::atomic<bool> isReady(false);
    
        int i = 1;
    
        std::thread t([&isReady, &i]()
        {
            i = 2;
            ANNOTATE_HAPPENS_BEFORE(&isReady);
            isReady = true;
        });
    
        while (!isReady)
            std::this_thread::yield();
    
        ANNOTATE_HAPPENS_AFTER(&isReady);
        i = 3;
    
        t.join();
    
        std::cout << i;
    
        return 0;
    }
    

    这里我们说 ANNOTATE_HAPPENS_BEFORE 的行始终发生在 ANNOTATE_HAPPENS_AFTER 行之前,我们知道由于检查程序逻辑,但是valgrind无法为你证明 .

    该计划产生:

    valgrind --tool=helgrind ./a.out
    ==714== Helgrind, a thread error detector
    ==714== Copyright (C) 2007-2015, and GNU GPL'd, by OpenWorks LLP et al.
    ==714== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
    ==714== Command: ./val
    ==714== 
    ==714== ---Thread-Announcement------------------------------------------
    ==714== 
    ==714== Thread #1 is the program's root thread
    ==714== 
    ==714== ---Thread-Announcement------------------------------------------
    ==714== 
    ==714== Thread #2 was created
    ==714==    at 0x59E169E: clone (in /usr/lib/libc-2.23.so)
    ==714==    by 0x4E421D9: create_thread (in /usr/lib/libpthread-2.23.so)
    ==714==    by 0x4E43C42: pthread_create@@GLIBC_2.2.5 (in /usr/lib/libpthread-2.23.so)
    ==714==    by 0x4C316F3: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
    ==714==    by 0x4C327D7: pthread_create@* (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
    ==714==    by 0x5113DB4: __gthread_create (gthr-default.h:662)
    ==714==    by 0x5113DB4: std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (thread.cc:163)
    ==714==    by 0x40109C: std::thread::thread<main::{lambda()#1}>(main::{lambda()#1}&&) (in /home/ennio/val)
    ==714==    by 0x400F55: main (in /home/ennio/val)
    ==714== 
    ==714== ----------------------------------------------------------------
    ==714== 
    ==714== Possible data race during read of size 1 at 0xFFF00061F by thread #1
    ==714== Locks held: none
    ==714==    at 0x401585: std::atomic<bool>::operator bool() const (in /home/ennio/val)
    ==714==    by 0x400F61: main (in /home/ennio/val)
    ==714== 
    ==714== This conflicts with a previous write of size 1 by thread #2
    ==714== Locks held: none
    ==714==    at 0x4015D5: std::__atomic_base<bool>::operator=(bool) (in /home/ennio/val)
    ==714==    by 0x401550: std::atomic<bool>::operator=(bool) (in /home/ennio/val)
    ==714==    by 0x400F1B: main::{lambda()#1}::operator()() const (in /home/ennio/val)
    ==714==    by 0x40146F: void std::_Bind_simple<main::{lambda()#1} ()>::_M_invoke<>(std::_Index_tuple<>) (in /home/ennio/val)
    ==714==    by 0x40140C: std::_Bind_simple<main::{lambda()#1} ()>::operator()() (in /home/ennio/val)
    ==714==    by 0x4013EB: std::thread::_State_impl<std::_Bind_simple<main::{lambda()#1} ()> >::_M_run() (in /home/ennio/val)
    ==714==    by 0x5113A9E: execute_native_thread_routine (thread.cc:83)
    ==714==    by 0x4C318E7: ??? (in /usr/lib/valgrind/vgpreload_helgrind-amd64-linux.so)
    ==714==  Address 0xfff00061f is on thread #1's stack
    ==714==  in frame #1, created by main (???:)
    ==714== 
    3==714== 
    ==714== For counts of detected and suppressed errors, rerun with: -v
    ==714== Use --history-level=approx or =none to gain increased speed, at
    ==714== the cost of reduced accuracy of conflicting-access information
    ==714== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
    

    要删除 isReady 本身的错误,我假设 __atomic_base<bool>::operator= 上的抑制文件就足够了 .

相关问题