c++multithreadingvalgrindcondition-variabledata-race

Why does version 3.22.0 of Valgrind-based Helgrind thread error detector reports data races and version 3.18.1 does not?


1.Background

The following C++ code from cppreference.com illustrates how std::condition_variable is used in combination with a std::mutex to facilitate inter-thread communication.

#include <condition_variable>                                            //01
#include <iostream>                                                      //02
#include <mutex>                                                         //03
#include <string>                                                        //04
#include <thread>                                                        //05
                                                                         //06
std::mutex m;                                                            //07
std::condition_variable cv;                                              //08
std::string data;                                                        //09
bool ready = false;                                                      //10
bool processed = false;                                                  //11
                                                                         //12
void worker_thread() {                                                   //13
  // wait until main() sends data                                        //14
  std::unique_lock<std::mutex> lk(m);                                    //15
  cv.wait(lk, [] { return ready; });                                     //16
                                                                         //17
  // after the wait, we own the lock                                     //18
  std::cout << "Worker thread is processing data\n";                     //19
  data += " after processing";                                           //20
                                                                         //21
  // send data back to main()                                            //22
  processed = true;                                                      //23
  std::cout << "Worker thread signals data processing completed\n";      //24
                                                                         //25
  // manual unlocking is done before notifying, to avoid waking up       //26 
  // the waiting thread only to block again (see notify_one for details) //27
  lk.unlock();                                                           //28
  cv.notify_one();                                                       //29
}                                                                        //30
                                                                         //31
int main() {                                                             //32
  std::thread worker(worker_thread);                                     //33
                                                                         //34
  data = "Example data";                                                 //35
  // send data to the worker thread                                      //36
  {                                                                      //37
    std::lock_guard<std::mutex> lk(m);                                   //38
    ready = true;                                                        //39
    std::cout << "main() signals data ready for processing\n";           //40
  }                                                                      //41
  cv.notify_one();                                                       //42
                                                                         //43
  // wait for the worker                                                 //44
  {                                                                      //42
    std::unique_lock<std::mutex> lk(m);                                  //46
    cv.wait(lk, [] { return processed; });                               //47
  }                                                                      //48
  std::cout << "Back in main(), data = " << data << '\n';                //49
                                                                         //50
  worker.join();                                                         //51
}                                                                        //52

2.Observations

Following observations assume above source code is stored in a file named a.cpp. These observations were made using WSL2 Ubuntu 22.03.4 (running on MS Windows 10).

2.1.LLVM Clang TSan (Thread Sanitizer) Version 14.0

No data race / race condition is reported when the instrumented code is executed:

$ clang++ -fsanitize=thread -g -O1 -Wall a.cpp; ./a.out

main() signals data ready for processing
Worker thread is processing data
Worker thread signals data processing completed
Back in main(), data = Example data after processing

2.2.Valgrind DRD (Data Race Detector) Tool Versions 3.18.1 and 3.22.0

$ clang++ -g -Wall a.cpp; valgrind --tool=drd ./a.out

drd, a thread error detector
Copyright (C) 2006-2020, and GNU GPL'd, by Bart Van Assche.
Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
Command: ./a.out

main() signals data ready for processing
 
  Probably a race condition: condition variable 0x10f118 has been signaled
  but the associated mutex 0x10f0f0 is not locked by the signalling thread.
  ┌────────────┬────────────────────────────────┬────────────────────────┬────┐
  │at 0x4E244C5│pthread_cond_signal_intercept   │drd_pthread_intercepts.c│1248│
  │by 0x4E244C5│pthread_cond_signal@*           │drd_pthread_intercepts.c│1261│
  │by 0x10A4D8 │main                            │a.cpp                   │  42│
  └────────────┴────────────────────────────────┴────────────────────────┴────┘
  cond 0x10f118 was first observed at:

  【TABLE 1】
  ┌────────────┬────────────────────────────────┬────────────────────────┬────┐
  │at 0x4E217F0│pthread_cond_wait_intercept     │drd_pthread_intercepts.c│1162│
  │by 0x4E217F0│pthread_cond_wait@*             │drd_pthread_intercepts.c│1170│
  │by 0x10A44C │void std                        │condition_variable      │ 103│
  │            │::condition_variable            │                        │    │
  │            │::wait<worker_thread()          │                        │    │
  │            │::$_0>(                         │                        │    │
  │            │  std::unique_lock<std::mutex>&,│                        │    │
  │            │  worker_thread()::$_0          │                        │    │
  │            │)                               │                        │    │
  │by 0x10A36B │worker_thread()                 │a.cpp                   │  16│
  │...         │                                │                        │    │
  └────────────┴────────────────────────────────┴────────────────────────┴────┘
 
   mutex 0x10f0f0 was first observed at:

  【TABLE 2】
  ┌────────────┬────────────────────────────────┬────────────────────────┬────┐
  │at 0x4E1B640│pthread_mutex_lock_intercept    │drd_pthread_intercepts.c│ 942│
  │by 0x4E1B640│pthread_mutex_lock@*            │drd_pthread_intercepts.c│ 955│
  │by 0x10A642 │__gthread_mutex_lock(           │gthr-default.h          │ 749│
  │            │  pthread_mutex_t*              │                        │    │
  │            │)                               │                        │    │
  │by 0x10A9C4 │std::mutex::lock()              │std_mutex.h             │ 100│
  │by 0x10AA6B │std::unique_lock<std::mutex>    │unique_lock.h           │ 139│
  │            │   ::lock()                     │                        │    │
  │by 0x10A720 │std::unique_lock<std::mutex>    │unique_lock.h           │  69│
  │            │   ::unique_lock(std::mutex&)   │                        │    │
  │by 0x10A35B │worker_thread()                 │a.cpp                   │  15│
  │...         │                                │                        │    │
  └────────────┴────────────────────────────────┴────────────────────────┴────┘

Worker thread is processing data
Worker thread signals data processing completed

  Thread 2:
  Probably a race condition: condition variable 0x10f118 has been signaled
  but the associated mutex 0x10f0f0 is not locked by the signalling thread.
  ┌────────────┬────────────────────────────────┬────────────────────────┬────┐
  │at 0x4E244C5│pthread_cond_signal_intercept   │drd_pthread_intercepts.c│1248│
  │by 0x4E244C5│pthread_cond_signal@*           │drd_pthread_intercepts.c│1261│
  │by 0x10A3D9 │worker_thread()                 │a.cpp                   │  29│
  │...         │                                │                        │    │
  └────────────┴────────────────────────────────┴────────────────────────┴────┘

  cond 0x10f118 was first observed at: See 【TABLE 1】

  mutex 0x10f0f0 was first observed at: See 【TABLE 2】

Back in main(), data = Example data after processing

  For lists of detected and suppressed errors, rerun with: -s
  ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 34 from 16)

2.3.Valgrind Helgrind (Thread Error Detector) Tool Versions 3.18.1 and 3.22.0

$ clang++ -g -Wall a.cpp; valgrind --tool=helgrind ./a.out 

Helgrind, a thread error detector
Copyright (C) 2007-2017, and GNU GPL'd, by OpenWorks LLP et al.
Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
Command: ./a.out

---Thread-Announcement------------------------------------------

Thread #1 is the program's root thread

---Thread-Announcement------------------------------------------

Thread #2 was created
   at 0x53729F3: clone (clone.S:76)
   by 0x53738EE: __clone_internal (clone-internal.c:83)
   by 0x52E16D8: create_thread (pthread_create.c:295)
   by 0x52E21FF: pthread_create@@GLIBC_2.34 (pthread_create.c:828)
   by 0x4E13585: pthread_create_WRK (hg_intercepts.c:445)
   by 0x4E14A8C: pthread_create@* (hg_intercepts.c:478)
   by 0x50FD328: std::thread::_M_start_thread(std::unique_ptr<std::thread::_State, std::default_delete<std::thread::_State> >, void (*)()) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
   by 0x10A849: std::thread::thread<void (&)(), , void>(void (&)()) (std_thread.h:143)
   by 0x10A477: main (a.cpp:33)

----------------------------------------------------------------

Possible data race during read of size 4 at 0x10F0F8 by thread #1
Locks held: none
   at 0x52E4F4A: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:94)
   by 0x4E1009A: mutex_lock_WRK (hg_intercepts.c:937)
   by 0x4E14E73: pthread_mutex_lock (hg_intercepts.c:960)
   by 0x10A642: __gthread_mutex_lock(pthread_mutex_t*) (gthr-default.h:749)
   by 0x10A9C4: std::mutex::lock() (std_mutex.h:100)
   by 0x10A8C2: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (std_mutex.h:229)
   by 0x10A49F: main (a.cpp:38)

This conflicts with a previous write of size 4 by thread #2
Locks held: none
   at 0x52E696C: __pthread_mutex_unlock_usercnt (pthread_mutex_unlock.c:62)
   by 0x52E08AD: __pthread_cond_wait_common (pthread_cond_wait.c:419)
   by 0x52E08AD: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
 Address 0x10f0f8 is 8 bytes inside data symbol "m"

----------------------------------------------------------------

Possible data race during write of size 4 at 0x10F0F8 by thread #1
Locks held: none
   at 0x52E4F5D: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:170)
   by 0x4E1009A: mutex_lock_WRK (hg_intercepts.c:937)
   by 0x4E14E73: pthread_mutex_lock (hg_intercepts.c:960)
   by 0x10A642: __gthread_mutex_lock(pthread_mutex_t*) (gthr-default.h:749)
   by 0x10A9C4: std::mutex::lock() (std_mutex.h:100)
   by 0x10A8C2: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (std_mutex.h:229)
   by 0x10A49F: main (a.cpp:38)

This conflicts with a previous write of size 4 by thread #2
Locks held: none
   at 0x52E696C: __pthread_mutex_unlock_usercnt (pthread_mutex_unlock.c:62)
   by 0x52E08AD: __pthread_cond_wait_common (pthread_cond_wait.c:419)
   by 0x52E08AD: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
 Address 0x10f0f8 is 8 bytes inside data symbol "m"

----------------------------------------------------------------

Possible data race during read of size 4 at 0x10F0FC by thread #1
Locks held: none
   at 0x52E4F60: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:172)
   by 0x4E1009A: mutex_lock_WRK (hg_intercepts.c:937)
   by 0x4E14E73: pthread_mutex_lock (hg_intercepts.c:960)
   by 0x10A642: __gthread_mutex_lock(pthread_mutex_t*) (gthr-default.h:749)
   by 0x10A9C4: std::mutex::lock() (std_mutex.h:100)
   by 0x10A8C2: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (std_mutex.h:229)
   by 0x10A49F: main (a.cpp:38)

This conflicts with a previous write of size 4 by thread #2
Locks held: none
   at 0x52E4F60: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:172)
   by 0x4E1009A: mutex_lock_WRK (hg_intercepts.c:937)
   by 0x4E14E73: pthread_mutex_lock (hg_intercepts.c:960)
   by 0x10A642: __gthread_mutex_lock(pthread_mutex_t*) (gthr-default.h:749)
   by 0x10A9C4: std::mutex::lock() (std_mutex.h:100)
   by 0x10AA6B: std::unique_lock<std::mutex>::lock() (unique_lock.h:139)
   by 0x10A720: std::unique_lock<std::mutex>::unique_lock(std::mutex&) (unique_lock.h:69)
   by 0x10A35B: worker_thread() (a.cpp:15)
 Address 0x10f0fc is 12 bytes inside data symbol "m"

----------------------------------------------------------------

Possible data race during write of size 4 at 0x10F0FC by thread #1
Locks held: none
   at 0x52E4F60: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:172)
   by 0x4E1009A: mutex_lock_WRK (hg_intercepts.c:937)
   by 0x4E14E73: pthread_mutex_lock (hg_intercepts.c:960)
   by 0x10A642: __gthread_mutex_lock(pthread_mutex_t*) (gthr-default.h:749)
   by 0x10A9C4: std::mutex::lock() (std_mutex.h:100)
   by 0x10A8C2: std::lock_guard<std::mutex>::lock_guard(std::mutex&) (std_mutex.h:229)
   by 0x10A49F: main (a.cpp:38)

This conflicts with a previous write of size 4 by thread #2
Locks held: none
   at 0x52E4F60: pthread_mutex_lock@@GLIBC_2.2.5 (pthread_mutex_lock.c:172)
   by 0x4E1009A: mutex_lock_WRK (hg_intercepts.c:937)
   by 0x4E14E73: pthread_mutex_lock (hg_intercepts.c:960)
   by 0x10A642: __gthread_mutex_lock(pthread_mutex_t*) (gthr-default.h:749)
   by 0x10A9C4: std::mutex::lock() (std_mutex.h:100)
   by 0x10AA6B: std::unique_lock<std::mutex>::lock() (unique_lock.h:139)
   by 0x10A720: std::unique_lock<std::mutex>::unique_lock(std::mutex&) (unique_lock.h:69)
   by 0x10A35B: worker_thread() (a.cpp:15)
 Address 0x10f0fc is 12 bytes inside data symbol "m"

main() signals data ready for processing
----------------------------------------------------------------

Possible data race during write of size 4 at 0x10F0F8 by thread #1
Locks held: none
   at 0x52E6A90: __pthread_mutex_unlock_usercnt (pthread_mutex_unlock.c:62)
   by 0x52E6A90: pthread_mutex_unlock@@GLIBC_2.2.5 (pthread_mutex_unlock.c:368)
   by 0x4E10869: mutex_unlock_WRK (hg_intercepts.c:1184)
   by 0x4E14E9F: pthread_mutex_unlock (hg_intercepts.c:1202)
   by 0x10A692: __gthread_mutex_unlock(pthread_mutex_t*) (gthr-default.h:779)
   by 0x10A9F4: std::mutex::unlock() (std_mutex.h:118)
   by 0x10A8E7: std::lock_guard<std::mutex>::~lock_guard() (std_mutex.h:235)
   by 0x10A4CC: main (a.cpp:42)

This conflicts with a previous write of size 4 by thread #2
Locks held: none
   at 0x52E696C: __pthread_mutex_unlock_usercnt (pthread_mutex_unlock.c:62)
   by 0x52E08AD: __pthread_cond_wait_common (pthread_cond_wait.c:419)
   by 0x52E08AD: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
 Address 0x10f0f8 is 8 bytes inside data symbol "m"

----------------------------------------------------------------

Thread #1: pthread_cond_{signal,broadcast}: dubious: associated lock is not held by any thread
   at 0x4E109E8: pthread_cond_signal_WRK (hg_intercepts.c:1567)
   by 0x4E14ED6: pthread_cond_signal@* (hg_intercepts.c:1588)
   by 0x10A4D8: main (a.cpp:43)

----------------------------------------------------------------

Possible data race during write of size 8 at 0x10F120 by thread #1
Locks held: none
   at 0x52E03F3: __atomic_wide_counter_add_relaxed (atomic_wide_counter.h:57)
   by 0x52E03F3: __condvar_add_g1_start_relaxed (pthread_cond_common.c:52)
   by 0x52E03F3: __condvar_quiesce_and_switch_g1 (pthread_cond_common.c:294)
   by 0x52E03F3: pthread_cond_signal@@GLIBC_2.3.2 (pthread_cond_signal.c:77)
   by 0x4E10A4A: pthread_cond_signal_WRK (hg_intercepts.c:1570)
   by 0x4E14ED6: pthread_cond_signal@* (hg_intercepts.c:1588)
   by 0x10A4D8: main (a.cpp:43)

This conflicts with a previous read of size 8 by thread #2
Locks held: none
   at 0x52E09E4: __atomic_wide_counter_load_relaxed (atomic_wide_counter.h:30)
   by 0x52E09E4: __condvar_load_g1_start_relaxed (pthread_cond_common.c:46)
   by 0x52E09E4: __pthread_cond_wait_common (pthread_cond_wait.c:486)
   by 0x52E09E4: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
   by 0x10AD44: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (std_thread.h:259)
 Address 0x10f120 is 8 bytes inside data symbol "cv"

----------------------------------------------------------------

Possible data race during write of size 4 at 0x10F0F8 by thread #1
Locks held: none
   at 0x52E696C: __pthread_mutex_unlock_usercnt (pthread_mutex_unlock.c:62)
   by 0x52E08AD: __pthread_cond_wait_common (pthread_cond_wait.c:419)
   by 0x52E08AD: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A60C: void std::condition_variable::wait<main::$_1>(std::unique_lock<std::mutex>&, main::$_1) (condition_variable:103)
   by 0x10A4FD: main (a.cpp:48)

This conflicts with a previous write of size 4 by thread #2
Locks held: none
   at 0x52E696C: __pthread_mutex_unlock_usercnt (pthread_mutex_unlock.c:62)
   by 0x52E08AD: __pthread_cond_wait_common (pthread_cond_wait.c:419)
   by 0x52E08AD: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
 Address 0x10f0f8 is 8 bytes inside data symbol "m"

----------------------------------------------------------------

Possible data race during read of size 8 at 0x10F120 by thread #2
Locks held: none
   at 0x52E0900: __atomic_wide_counter_load_relaxed (atomic_wide_counter.h:30)
   by 0x52E0900: __condvar_load_g1_start_relaxed (pthread_cond_common.c:46)
   by 0x52E0900: __pthread_cond_wait_common (pthread_cond_wait.c:539)
   by 0x52E0900: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
   by 0x10AD44: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (std_thread.h:259)
   by 0x10AD14: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (std_thread.h:266)
   by 0x10AC78: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (std_thread.h:211)
   by 0x50FD252: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)
   by 0x4E13779: mythread_wrapper (hg_intercepts.c:406)

This conflicts with a previous write of size 8 by thread #1
Locks held: none
   at 0x52E03F3: __atomic_wide_counter_add_relaxed (atomic_wide_counter.h:57)
   by 0x52E03F3: __condvar_add_g1_start_relaxed (pthread_cond_common.c:52)
   by 0x52E03F3: __condvar_quiesce_and_switch_g1 (pthread_cond_common.c:294)
   by 0x52E03F3: pthread_cond_signal@@GLIBC_2.3.2 (pthread_cond_signal.c:77)
   by 0x4E10A4A: pthread_cond_signal_WRK (hg_intercepts.c:1570)
   by 0x4E14ED6: pthread_cond_signal@* (hg_intercepts.c:1588)
   by 0x10A4D8: main (a.cpp:43)
 Address 0x10f120 is 8 bytes inside data symbol "cv"

----------------------------------------------------------------

Possible data race during read of size 4 at 0x10F0F8 by thread #2
Locks held: none
   at 0x52E41DB: __pthread_mutex_cond_lock (pthread_mutex_lock.c:94)
   by 0x52E0933: __pthread_cond_wait_common (pthread_cond_wait.c:616)
   by 0x52E0933: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
   by 0x10AD44: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (std_thread.h:259)
   by 0x10AD14: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (std_thread.h:266)
   by 0x10AC78: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (std_thread.h:211)
   by 0x50FD252: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)

This conflicts with a previous write of size 4 by thread #1
Locks held: none
   at 0x52E696C: __pthread_mutex_unlock_usercnt (pthread_mutex_unlock.c:62)
   by 0x52E08AD: __pthread_cond_wait_common (pthread_cond_wait.c:419)
   by 0x52E08AD: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A60C: void std::condition_variable::wait<main::$_1>(std::unique_lock<std::mutex>&, main::$_1) (condition_variable:103)
   by 0x10A4FD: main (a.cpp:48)
 Address 0x10f0f8 is 8 bytes inside data symbol "m"

----------------------------------------------------------------

Possible data race during write of size 4 at 0x10F0F8 by thread #2
Locks held: none
   at 0x52E41EE: __pthread_mutex_cond_lock (pthread_mutex_lock.c:170)
   by 0x52E0933: __pthread_cond_wait_common (pthread_cond_wait.c:616)
   by 0x52E0933: pthread_cond_wait@@GLIBC_2.3.2 (pthread_cond_wait.c:627)
   by 0x4E1390B: pthread_cond_wait_WRK (hg_intercepts.c:1291)
   by 0x4E14EAA: pthread_cond_wait@* (hg_intercepts.c:1318)
   by 0x10A44C: void std::condition_variable::wait<worker_thread()::$_0>(std::unique_lock<std::mutex>&, worker_thread()::$_0) (condition_variable:103)
   by 0x10A36B: worker_thread() (a.cpp:16)
   by 0x10ADD6: void std::__invoke_impl<void, void (*)()>(std::__invoke_other, void (*&&)()) (invoke.h:61)
   by 0x10AD6C: std::__invoke_result<void (*)()>::type std::__invoke<void (*)()>(void (*&&)()) (invoke.h:96)
   by 0x10AD44: void std::thread::_Invoker<std::tuple<void (*)()> >::_M_invoke<0ul>(std::_Index_tuple<0ul>) (std_thread.h:259)
   by 0x10AD14: std::thread::_Invoker<std::tuple<void (*)()> >::operator()() (std_thread.h:266)
   by 0x10AC78: std::thread::_State_impl<std::thread::_Invoker<std::tuple<void (*)()> > >::_M_run() (std_thread.h:211)
   by 0x50FD252: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.30)

This conflicts with a previous write of size 4 by thread #1
Locks held: none
   ...
   by 0x10A4FD: main (a.cpp:48)
 Address 0x10f0f8 is 8 bytes inside data symbol "m"

Worker thread is processing data
Worker thread signals data processing completed
----------------------------------------------------------------

Thread #2: pthread_cond_{signal,broadcast}: dubious: associated lock is not held by any thread
   ...
   by 0x10A3D9: worker_thread() (a.cpp:29)

----------------------------------------------------------------

Possible data race during write of size 8 at 0x10F120 by thread #2
Locks held: none
   ...
   by 0x10A3D9: worker_thread() (a.cpp:29)
     ...

This conflicts with a previous read of size 8 by thread #1
Locks held: none
   ...
   by 0x10A4FD: main (a.cpp:48)
 Address 0x10f120 is 8 bytes inside data symbol "cv"

----------------------------------------------------------------

Possible data race during read of size 8 at 0x10F120 by thread #1
Locks held: none
   ...
   by 0x10A4FD: main (a.cpp:48)

This conflicts with a previous write of size 8 by thread #2
Locks held: none
   ...
 Address 0x10f120 is 8 bytes inside data symbol "cv"

Back in main(), data = Example data after processing

Use --history-level=approx or =none to gain increased speed, at
the cost of reduced accuracy of conflicting-access information
For lists of detected and suppressed errors, rerun with: -s
ERROR SUMMARY: 19 errors from 14 contexts (suppressed: 0 from 0)

3.Question

How to resolve errors reported by version 3.22.0 of Helgrind that DRD does not report?


Solution

  • Some partial answers.

    1. No you probably can't change your code, at least if you keep using std::thread. The issue is within libstdc++ (and probably libc++ as well).
    2. Valgrind only deals with pthreads and Qt threads. If you are using Boost threads, std::thread or C thrd they all boil down to pthreads. The issue is how the pthread condition variables are being used. So you probably could write pthread code that doesn't produce these errors.
    3. For the moment I see two sulutions: suppression or modification of Valgrind to relax these checks. Ideally I'd like the best of both worlds, keep these errors for C code using pthreads directly but filter them out when they come from libstdc++ and libc++.

    Also, for Helgrind the changes since 18.1 include

    Most of them are related to libstdc++ using new pthread timed functions that Helgrind didn't handle previously.

    Update: The example code on my systems (FreeBSD arm64 and amd64) generates 2 errors for both DRD and Helgrind. That's with Valgrind built from git VALGRIND_3_22_0-240-gcbc2b1d0e.