# Valgrind helgrind **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. ```bash 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: ```c 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: ```c // 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: ```c #include // 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.