helgrind detects data races and lock-ordering problems in multithreaded programs (pthreads, OpenMP). A data race occurs when two threads access the same memory location without synchronization—at least one is a write. Even if the race doesn't cause a visible crash, it's undefined behavior and a correctness bug.
valgrind --tool=helgrind ./threaded_program
helgrind instruments all memory accesses and tracks which thread touched which location. When it sees two threads access the same location without a happens-before relationship (established by locks or synchronization primitives), it reports a race.
Example race: Two threads writing to the same counter without a lock:
int counter = 0; void* increment(void* arg) { for (int i = 0; i < 1000; i++) counter++; // data race: no lock }
Both threads access counter without synchronization. On a multicore CPU, the increments can interleave, causing lost updates. helgrind reports:
Thread #2: Possible data race during write of size 4 at 0x...: increment (program.c:7) Conflicting load by thread #1 at 0x...: increment (program.c:7)
Lock-ordering problems: helgrind also detects deadlocks caused by acquiring locks in different orders:
// Thread 1: acquire lock_a, then lock_b // Thread 2: acquire lock_b, then lock_a
This is a potential deadlock. helgrind reports “Lock order violated” when it observes inconsistent ordering across threads.
False positives: helgrind can report races in code that's actually thread-safe due to subtle synchronization (memory barriers, atomic operations, careful locking). Use annotations to tell helgrind about custom synchronization:
#include <valgrind/helgrind.h> // Tell helgrind this variable is protected by a lock ANNOTATE_CONDVAR_LOCK_CREATE(lock); ANNOTATE_BENIGN_RACE(&global_var, "protected by my_lock");
Suppressions are more common for helgrind than memcheck because library code sometimes has benign races or uses synchronization Valgrind doesn't understand.
Reporting: helgrind's output includes the conflicting accesses with stack traces for both threads. The pattern is the same as memcheck's format—function names, source files, line numbers.
Usage notes: helgrind is slower than memcheck but not as slow as memcheck with --track-origins. For multithreaded programs, run helgrind periodically (especially during development) to catch races. In large MPI or OpenMP codes, run helgrind on the single-threaded version first to catch memory bugs before debugging races.