Hello! Welcome back.
In our last session, we wrapped up our exploration of Valgrind's thread error detectors by looking at DRD. We saw how it complements Helgrind by excelling at finding Pthreads API misuse and performance-sapping lock contention. This completed our tour of tools for finding concurrency-specific bugs.
Today, we'll take one final, crucial step in our debugging module. We'll return to Valgrind's most famous tool, memcheck, and apply it to the unique memory management challenges of multithreaded programs. The learning outcome is to use Valgrind's memcheck to detect memory leaks specific to thread resources (e.g., un-joined threads, un-destroyed mutexes). Even a perfectly synchronized program can be brought down by slow and steady resource leaks, and memcheck is our primary tool for finding them.
A Quick Refresher on Valgrind's memcheck
Given your background, you're likely familiar with using memcheck to find memory leaks from mismatched malloc/free calls in single-threaded programs. The core idea is the same in multithreaded code, but the sources of leaks expand beyond simple malloc calls.
For a very quick refresher on how memcheck presents its findings, you can watch this short segment.
Using Valgrind and GDB together to fix a segfault and memory leak
This video from Mike Shah offers a concise demonstration of using memcheck to find and fix a basic memory leak.
Watch from 05:04 to 08:10. Focus on the command used (valgrind --leak-check=full) and the key sections of the output: the 'HEAP SUMMARY' and the 'LEAK SUMMARY', particularly the 'definitely lost' category and the associated stack trace that pinpoints the allocation site.
The key is running valgrind with the --leak-check=full flag and looking for non-zero values in the leak summary. Now, let's apply this to thread-specific resources.
Leak #1: The Un-joined Thread
When you create a thread with pthread_create, the operating system allocates a significant amount of resources for it, including its own stack space and internal management structures. A common mistake is to create threads and simply let them finish their work without explicitly cleaning up these resources.
Consider this program:
// thread_leak.c
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
void* thread_func(void* arg) {
printf("Thread is running and will now exit.\n");
return NULL;
}
int main() {
pthread_t t1;
printf("Main: Creating a thread.\n");
pthread_create(&t1, NULL, thread_func, NULL);
// We "forget" to join or detach the thread.
// Let's sleep for a bit to ensure the thread runs and exits.
sleep(1);
printf("Main: Program finished.\n");
return 0;
}
Let's compile and check this for leaks:
gcc -g -o thread_leak thread_leak.c -pthreadvalgrind --leak-check=full ./thread_leak
You will see an output from Valgrind that reports leaked memory. The key part will look something like this, pointing directly at pthread_create:
==12345== LEAK SUMMARY:
==12345== definitely lost: 0 bytes in 0 blocks
==12345== indirectly lost: 0 bytes in 0 blocks
==12345== possibly lost: 8,320 bytes in 1 blocks
==12345== still reachable: 1,152 bytes in 1 blocks
==12345==
==12345== ... bytes in 1 blocks are possibly lost in loss record X of Y
==12345== at 0x........: calloc (vg_replace_malloc.c:...)
==12345== by 0x........: _dl_allocate_tls (dl-tls.c:...)
==12345== by 0x........: pthread_create@@GLIBC_... (allocatestack.c:...)
==12345== by 0x........: main (thread_leak.c:13)
Valgrind is telling us that memory allocated during pthread_create was never freed. Why? The following resource provides the exact answer.
valgrind memory leak errors when using pthread_create
This Stack Overflow question deals with the precise problem we're observing: Valgrind reporting leaks associated with pthread_create.
Read the user's question to see a Valgrind report very similar to ours. Then, focus on the highest-voted answer, which explains why this happens and what the solutions are.
As the resource explains, a thread's resources are not automatically reclaimed upon its termination. They are held in a zombie-like state so that another thread can retrieve its return value. To clean up these resources, you must explicitly do one of two things:
- Join the thread: Call
pthread_join(t1, NULL). This synchronizes with the thread's termination and reclaims its resources. - Detach the thread: Call
pthread_detach(t1). This tells the system that you will never join this thread, so its resources can be freed automatically as soon as it terminates.
Let's fix our code using pthread_join:
// thread_leak_fixed.c
#include <pthread.h>
#include <stdio.h>
void* thread_func(void* arg) {
printf("Thread is running and will now exit.\n");
return NULL;
}
int main() {
pthread_t t1;
printf("Main: Creating a thread.\n");
pthread_create(&t1, NULL, thread_func, NULL);
// The fix: Join the thread to reclaim its resources.
pthread_join(t1, NULL);
printf("Main: Program finished.\n");
return 0;
}
Compile and run this new version under memcheck. The leak report will be gone, and you'll see the satisfying message: All heap blocks were freed -- no leaks are possible.
Leak #2: The Dynamically Allocated Mutex
The same principle of manual resource management applies to synchronization primitives like mutexes, condition variables, and read-write locks if you allocate them on the heap.
Consider a situation where you need to malloc a mutex, perhaps as part of a larger dynamically allocated struct.
// mutex_leak.c
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
pthread_mutex_t* my_mutex;
// Dynamically allocate the mutex
my_mutex = (pthread_mutex_t*) malloc(sizeof(pthread_mutex_t));
if (my_mutex == NULL) { return 1; }
// Initialize and use the mutex...
pthread_mutex_init(my_mutex, NULL);
pthread_mutex_lock(my_mutex);
// Critical section...
pthread_mutex_unlock(my_mutex);
// Clean up Pthreads resources
pthread_mutex_destroy(my_mutex);
// But we forget to free the memory!
printf("Finished.\n");
return 0;
}
A common misconception is that pthread_mutex_destroy also frees the memory pointed to by my_mutex. This is incorrect. Running memcheck on this program will confirm it:
gcc -g -o mutex_leak mutex_leak.c -pthreadvalgrind --leak-check=full ./mutex_leak
You will get a definitely lost report for exactly sizeof(pthread_mutex_t) bytes (e.g., 40 bytes on a 64-bit system), originating from the malloc call.

Why doesn't pthread_mutex_destroy handle this for us? The answer lies in the design of the Pthreads API.
Do I need to use delete after pthread_mutex_destroy
This Stack Overflow discussion directly addresses whether one needs to free the memory for a dynamically allocated mutex after destroying it.
Read the question and the top answer. The answer provides the crucial piece of logic: pthread_mutex_destroy cannot free the memory because it doesn't know if the mutex was allocated on the stack or the heap.
As the resource clarifies, cleanup is a two-step process for heap-allocated Pthreads objects:
- Destroy: Call
pthread_mutex_destroy()to release any resources the Pthreads library associated with the object. - Free: Call
free()to release the memory that the object itself occupies.
The correct cleanup sequence is:
// Correct cleanup
pthread_mutex_destroy(my_mutex);
free(my_mutex);
Adding the free(my_mutex); call to our program will resolve the memory leak.
Test your understanding!
You are reviewing code and see the following snippet for cleaning up a dynamically allocated mutex.
free(my_mutex);
pthread_mutex_destroy(my_mutex);
Is this correct? If not, what is the potential problem?
Show answer
This is incorrect and represents a major bug. After free(my_mutex) is called, the memory pointed to by my_mutex is returned to the system and is no longer valid for your program to access. The subsequent call to pthread_mutex_destroy(my_mutex) will attempt to access this freed memory, leading to undefined behavior. It could cause a segmentation fault or corrupt the heap. The correct order is always destroy first, then free.
Conclusion
You have now completed your tour of the Valgrind suite for multithreaded debugging. By combining memcheck with Helgrind and DRD, you have a powerful, systematic approach to building correct and robust concurrent applications.
Key Takeaways:
- Thread Resources Leak: Threads created with
pthread_createconsume resources that must be reclaimed. Failure to call eitherpthread_joinorpthread_detachwill result in a memory leak thatmemcheckwill report. - Dynamic Primitives Leak: When you
malloca synchronization primitive like apthread_mutex_t, you are responsible for freeing its memory. - Destroy is Not Free: Functions like
pthread_mutex_destroyrelease internal Pthreads resources but do notfreethe memory of the object itself. - Correct Cleanup Order: For dynamically allocated primitives, you must first call the
destroyfunction, and then callfreeon the pointer.
Preview of the Next Lesson:
Our focus now shifts from correctness to performance. While Valgrind's DRD can spot contention, dedicated profiling tools are needed to get a complete picture of your application's behavior. In the next module, we will kick off our study of optimization by learning how to profile a multithreaded application using tools like perf to identify performance hotspots.