Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to avoid false positives with Helgrind?

My thread synchronisation "style" seems to be throwing helgrind off. Here's a simple program that reproduces the problem:

#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;
}

As far as I can tell, the above is a perfectly well-formed program. However, when I run helgrind using the following command I get errors:

valgrind --tool=helgrind ./a.out

The output of this is:

==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 seems to be picking up my while loop as a race condition. How am I supposed to form this program to avoid helgrind throwing out false positives?

like image 213
quant Avatar asked Aug 19 '16 11:08

quant


People also ask

How does Helgrind work?

Helgrind monitors the order in which threads acquire locks. This allows it to detect potential deadlocks which could arise from the formation of cycles of locks.

Which of the following is are detect multithreading issues in Helgrind and DRD?

Helgrind and DRD are both thread error detectors. This basically means that they will attempt to detect any multithreading issues such as data races and incorrect use of mutexes.

Does Valgrind work with threads?

Overview. DRD is a Valgrind tool for detecting errors in multithreaded C and C++ programs. The tool works for any program that uses the POSIX threading primitives or that uses threading concepts built on top of the POSIX threading primitives.


1 Answers

The problem is that Helgrind doesn't understand GCC's atomic builtins, so doesn't realise that they are race-free and impose ordering on the program.

There are ways to annotate your code to help Helgrind, see http://valgrind.org/docs/manual/hg-manual.html#hg-manual.effective-use (but I'm not sure how to use them here, I already tried what sbabbi shows and it only solves part of the problem).

I would avoid yielding in a busy loop anyway, it's a poor form of synchronization. It could be done with a condition variable like so:

#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;
}
like image 117
Jonathan Wakely Avatar answered Oct 31 '22 18:10

Jonathan Wakely