malloc
thread-safety
multithreading
memory management
concurrency

Is malloc thread-safe?

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

Introduction

On modern mainstream systems, malloc is generally thread-safe in the narrow sense that concurrent allocation and free calls do not corrupt the allocator’s internal bookkeeping. That guarantee is useful, but it is also easy to misunderstand. Allocator thread safety does not make the memory you allocate automatically safe for unsynchronized sharing between threads.

What Thread-Safe Means Here

When people say malloc is thread-safe, they mean the C runtime protects its own allocator state while multiple threads call allocation functions at the same time.

This is a normal and safe pattern:

c
1#include <pthread.h>
2#include <stdio.h>
3#include <stdlib.h>
4
5void* worker(void* arg) {
6    int* value = malloc(sizeof(int));
7    if (!value) {
8        return NULL;
9    }
10
11    *value = 42;
12    printf("thread value: %d\n", *value);
13    free(value);
14    return NULL;
15}
16
17int main(void) {
18    pthread_t t1, t2;
19    pthread_create(&t1, NULL, worker, NULL);
20    pthread_create(&t2, NULL, worker, NULL);
21    pthread_join(t1, NULL);
22    pthread_join(t2, NULL);
23    return 0;
24}

Each thread allocates and frees its own memory, and the allocator handles concurrent use correctly.

What It Does Not Mean

The returned pointer is still just ordinary memory. If several threads read or write the same allocated object, you still need your own synchronization.

c
1#include <pthread.h>
2#include <stdio.h>
3#include <stdlib.h>
4
5static int* counter;
6static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
7
8void* increment(void* arg) {
9    for (int i = 0; i < 100000; i++) {
10        pthread_mutex_lock(&lock);
11        (*counter)++;
12        pthread_mutex_unlock(&lock);
13    }
14    return NULL;
15}
16
17int main(void) {
18    pthread_t a, b;
19    counter = malloc(sizeof(int));
20    if (!counter) {
21        return 1;
22    }
23    *counter = 0;
24
25    pthread_create(&a, NULL, increment, NULL);
26    pthread_create(&b, NULL, increment, NULL);
27    pthread_join(a, NULL);
28    pthread_join(b, NULL);
29
30    printf("counter: %d\n", *counter);
31    free(counter);
32    return 0;
33}

Here the mutex protects the shared object. malloc did not solve that problem for us.

The Real Multithreaded Risk Is Usually Lifetime

In concurrent code, the hardest memory bugs are often not allocator failures. They are ownership and lifetime bugs such as:

  • use-after-free
  • double free
  • one thread freeing memory while another still uses it
  • stale pointers after ownership moves

Those are application design problems, not allocator thread-safety problems. A good concurrent design defines clearly who owns an allocation and when another thread may safely access or release it.

Performance Is a Different Question

Even when malloc is correct under concurrency, it can still become a performance bottleneck. Heavy allocation traffic may create contention, cache-line movement, or fragmentation.

If profiling shows allocation hot spots, options include:

  • reducing allocation frequency
  • using object pools carefully
  • using arena allocators for structured lifetimes
  • relying on thread-local caches where the allocator or design supports them

But that is a performance discussion, not a correctness requirement. Do not replace malloc prematurely just because your code is multithreaded.

Practical Guidance

A good rule set is:

  • trust the platform allocator for ordinary concurrent allocation and free
  • synchronize access to shared objects yourself
  • define ownership and lifetime rules clearly
  • use sanitizers to catch races and invalid memory use early

AddressSanitizer and ThreadSanitizer are usually more valuable than speculative allocator rewrites.

Common Pitfalls

The most common mistake is assuming that because malloc is thread-safe, the objects it returns are safe for simultaneous mutation from many threads. They are not.

Another pitfall is adding one global lock around every allocation call. That often harms performance without solving the real bug.

Developers also frequently blame the allocator for crashes that are actually use-after-free or unsynchronized shared-state access in application code.

Finally, if one thread frees memory while another still holds a pointer to it, allocator thread safety will not save you. That is still undefined behavior.

Summary

  • 'malloc is typically thread-safe on modern systems for concurrent allocation and free.'
  • That guarantee protects allocator internals, not your shared data structures.
  • Access to shared allocated objects still needs synchronization.
  • Most threaded memory bugs come from ownership and lifetime mistakes.
  • Optimize allocation strategy only after profiling shows a real contention problem.

Related reading
Course
Intermediate
27 lessons
15 hours
DSA Fundamentals

Master algorithmic patterns and data structures through hands-on LeetCode-style problems - from arrays and hashing to dynamic programming and advanced graphs.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Data Structures & Algorithms practice on Codemia

Step through 300 algorithm problems with animated visualisers that show the data structure changing as the code runs.

Practice algorithms

All Rights Reserved.