Почему версия 3.22.0 детектора ошибок потоков Helgrind на основе Valgrind сообщает о гонках данных, а версия 3.18.1 — нет?

1. История

Следующий код C++ с сайта cppreference.com иллюстрирует, как std::condition_variable используется в сочетании с std::mutex для облегчения межпотокового взаимодействия.

#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.Наблюдения

Следующие наблюдения предполагают, что приведенный выше исходный код хранится в файле с именем a.cpp. Эти наблюдения были сделаны с использованием WSL2 Ubuntu 22.03.4 (работающей под управлением MS Windows 10).

2.1.LLVM Clang Tsan (очиститель резьбы) Версия 14.0

При выполнении инструментированного кода не сообщается о гонке данных/состоянии гонки:

$ 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 (Детектор гонки данных) Версии 3.18.1 и 3.22.0

  • Сообщается о 2 ошибках:
$ 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)
  • Эти ошибки исчезнут, если внести следующие изменения, чтобы мьютекс m не разблокировался перед вызовом функции cv.notify_one():

    • Строка 28 закомментирована.

    • Строка 42 заменена на 41.

  • Эти два изменения кода основаны на следующих ссылках:

    • Ответ от Wandering Logic на вопрос Есть ли в этом коде гонка данных?.
    • Проблема в проекте Github isocpp/CppCoreGuidelines.

2.3.Valgrind Helgrind (Детектор ошибок потока) Версии 3.18.1 и 3.22.0

  • Сообщается о 19 ошибках:
$ 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)
  • После выполнения двух изменений кода, упомянутых в разделе 2.2:

    • Все ошибки исчезают с версией Helgrind 3.18.1.

    • В версии Helgrind 3.22.0 осталось 18 ошибок.

3.Вопрос

Как устранить ошибки, сообщаемые версией 3.22.0 Helgrind, о которых не сообщает DRD?

  • Должны ли быть внесены дополнительные изменения в исходный код для обеспечения соответствия внутренней бизнес-логике Helgrind?

  • Следует ли использовать другую реализацию API потоков для C++ вместо std::thread (т. е. POSIX Threads, Boost.Thread, ...)?

  • Следует ли использовать команду Valgrind с опцией --suppressions (в случае, если 18 ошибок, о которых она сообщает, являются ложными срабатываниями)?

  • Что-нибудь еще ?

Я проверил еще раз, на моей машине измененный код (cv.notify_one всегда под блокировкой) не выдает никаких ошибок от valgrind (ни DRD, ни инструмента Helgrind). Перед запуском с valgrind убедитесь, что вы компилируете без флага -sanitize, эти инструменты несовместимы друг с другом.

pptaszni 20.03.2024 13:11

@pptaszni, спасибо, что нашли время проверить. Флаг -sanitize не используется. Могу ли я узнать название и номер версии следующего программного обеспечения, работающего на вашем компьютере: ① ОС, ② Компилятор C++, ③ Valgrind.

Fabien Launay 20.03.2024 15:11

Конечно, это WSL2 Ubuntu22.04 с хостом Windows10, компилятором g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, valgrind-3.18.1, флагом -g -O2 -pedantic -Wall -Wextra.

pptaszni 20.03.2024 15:36

@pptaszni, спасибо за ответ. Очень полезно. Я немного изменил заголовок, а также разделы 2 и 3 вопроса, чтобы подчеркнуть разрыв между версиями 3.18.1 и 3.22.0 отчетов об ошибках Helgrind.

Fabien Launay 21.03.2024 00:13

Я не уверен, что произошло между 3.18.1 и 3.22. В противном случае мне нужно связаться с разработчиками libstdc++ и libc++, чтобы узнать их мнение.

Paul Floyd 25.03.2024 09:11
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
173
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Некоторые частичные ответы.

  1. Нет, вы, вероятно, не сможете изменить свой код, по крайней мере, если продолжите использовать std::thread. Проблема заключается в libstdc++ (и, возможно, также в libc++).
  2. Valgrind имеет дело только с потоками pthreads и Qt. Если вы используете потоки Boost, std::thread или C thrd, все они сводятся к pthreads. Проблема в том, как используются переменные условия pthread. Таким образом, вы, вероятно, могли бы написать код pthread, который не выдает этих ошибок.
  3. На данный момент я вижу два решения: подавление или модификация Valgrind, чтобы ослабить эти проверки. В идеале я хотел бы получить лучшее из обоих миров: сохранить эти ошибки для кода C, используя напрямую pthreads, но отфильтровать их, когда они поступают из libstdc++ и libc++.

Кроме того, для Хелгринда изменения с версии 18.1 включают:

  • Ошибка 445504. Использование условной переменной C++ приводит к фиктивному выводу «мьютекс блокируется одновременно двумя потоками».
  • Ошибка 446139 DRD/Helgrind с ложными срабатываниями std::shared_timed_mutex::try_lock_until и try_lock_shared_until.
  • Ошибка 446138 DRD/Helgrind с std::timed_mutex::try_lock_until ложными срабатываниями
  • Ошибка 392331 — ложная ошибка блокировки не удерживается изнутри pthread_cond_timedwait
  • Ошибка 400793 — ложное срабатывание pthread_rwlock_timedwrlock.
  • Ошибка 327548 — ложное срабатывание при разрушении мьютекса.

Большинство из них связаны с libstdc++ с использованием новых синхронизируемых функций pthread, которые Helgrind ранее не обрабатывал.

Обновлять: Пример кода в моих системах (FreeBSD Arm64 и amd64) генерирует 2 ошибки как для DRD, так и для Helgrind. Это касается Valgrind, созданного на основе git VALGRIND_3_22_0-240-gcbc2b1d0e.

Другие вопросы по теме