# Thread-Safety Guarantees of mulle_allocator Functions: Default vs Custom Behavior

> Explore mulle_allocator thread-safety guarantees. Understand default allocator behavior via libc and learn how custom allocators need external synchronization for safe multithreaded use.

- Repository: [mulle-c/mulle-allocator](https://github.com/mulle-c/mulle-allocator)
- Tags: internals
- Published: 2026-03-07

---

**mulle_allocator functions inherit thread-safety from the underlying allocator implementation—the default allocator is thread-safe by delegating to libc, but custom allocators require external synchronization.**

The `mulle-c/mulle-allocator` library provides a lightweight abstraction over memory allocation that stores function pointers in a `struct mulle_allocator`. Understanding the thread-safety guarantees of mulle_allocator functions is essential for concurrent applications, as the library implements no internal locking mechanisms itself.

## Default Allocator Thread-Safety

The **default allocator** (`mulle_allocator_default` and `mulle_allocator_stdlib`) forwards all calls directly to the standard C library functions `malloc`, `calloc`, `realloc`, and `free`. According to the implementation in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) (lines 34-52), these global instances are initialized with stdlib function pointers:

```c
// From src/mulle-allocator.c#L34-L52
struct mulle_allocator   mulle_allocator_stdlib =
{
   .calloc   = calloc,
   .realloc  = realloc,
   .free     = free,
   .fail     = mulle_allocation_fail,
   .abafree  = NULL,
   .aba      = NULL
};

```

Modern C runtime libraries guarantee that `malloc`, `free`, and related functions are thread-safe (re-entrant and internally serialized). Consequently, calls to `mulle_malloc()` and `mulle_free()` are safe for concurrent use from multiple threads without additional locking.

### Concurrent Usage Example

The following example demonstrates safe multi-threaded allocation using the default allocator:

```c
#include <mulle-allocator/mulle-allocator.h>
#include <pthread.h>
#include <stdio.h>

void *thread_func(void *arg)
{
    /* Each thread allocates and frees its own block */
    void *p = mulle_malloc(1024);          // forwards to malloc()
    /* ... use the memory ... */
    mulle_free(p);                         // forwards to free()
    return NULL;
}

int main(void)
{
    const int N = 8;
    pthread_t th[N];

    for (int i = 0; i < N; ++i)
        pthread_create(&th[i], NULL, thread_func, NULL);

    for (int i = 0; i < N; ++i)
        pthread_join(th[i], NULL);

    printf("All threads completed without explicit locking.\n");
    return 0;
}

```

This works because `mulle_malloc` and `mulle_free` invoke the default allocator, which uses the thread-safe libc implementation.

## Custom Allocator Thread-Safety Requirements

When providing a **custom** `struct mulle_allocator`, the library assumes the supplied callbacks are well-behaved. The public API functions (`mulle_allocator_malloc`, `mulle_allocator_calloc`, `mulle_allocator_realloc`, `mulle_allocator_free`) are thin inline wrappers that immediately dereference the function pointer and forward the call. Consequently, thread safety is the responsibility of the custom allocator—the wrapper provides no protection.

### Implementing Thread-Safe Custom Allocators

To create a thread-safe custom allocator, you must implement your own synchronization around the allocation logic:

```c
#include <mulle-allocator/mulle-allocator.h>
#include <pthread.h>
#include <stdlib.h>

static pthread_mutex_t my_lock = PTHREAD_MUTEX_INITIALIZER;

/* Thread-safe wrappers around the standard library */
static void *my_malloc(struct mulle_allocator *a, size_t size)
{
    (void)a;
    pthread_mutex_lock(&my_lock);
    void *p = malloc(size);
    pthread_mutex_unlock(&my_lock);
    return p;
}

static void my_free(struct mulle_allocator *a, void *ptr)
{
    (void)a;
    pthread_mutex_lock(&my_lock);
    free(ptr);
    pthread_mutex_unlock(&my_lock);
}

static void *my_calloc(struct mulle_allocator *a, size_t n, size_t size)
{
    (void)a;
    pthread_mutex_lock(&my_lock);
    void *p = calloc(n, size);
    pthread_mutex_unlock(&my_lock);
    return p;
}

static void *my_realloc(struct mulle_allocator *a, void *ptr, size_t size)
{
    (void)a;
    pthread_mutex_lock(&my_lock);
    void *p = realloc(ptr, size);
    pthread_mutex_unlock(&my_lock);
    return p;
}

/* Build the custom allocator */
static struct mulle_allocator my_threadsafe_alloc = {
    .calloc   = my_calloc,
    .realloc  = my_realloc,
    .free     = my_free,
    .fail     = mulle_allocation_fail,
    .abafree  = NULL,
    .aba      = NULL
};

void *worker(void *unused)
{
    void *p = mulle_allocator_malloc(&my_threadsafe_alloc, 256);
    /* ... */
    mulle_allocator_free(&my_threadsafe_alloc, p);
    return NULL;
}

```

In this implementation, the `pthread_mutex` explicitly serializes all allocation calls. The `mulle_allocator_*` API remains re-entrant because the underlying callbacks are now thread-safe.

## Global Default Replacement Hazards

The global instance `mulle_allocator_default` can be swapped at runtime to redirect all default allocations to a custom implementation. However, this replacement operation is **not thread-safe**. The documentation in [`assets/dox/TOC.md`](https://github.com/mulle-c/mulle-allocator/blob/main/assets/dox/TOC.md) (lines 59-62) explicitly states that replacement must be performed when no other thread is using the allocator, typically during program start-up.

```c
#include <mulle-allocator/mulle-allocator.h>

/* Assume we have another allocator implementation `my_allocator` */
extern struct mulle_allocator my_allocator;

/* Replace the global default – do this before any other thread starts */
void install_my_allocator(void)
{
    /* Not thread-safe! Must be called when no other thread is using the allocator */
    mulle_allocator_default = my_allocator;
}

```

Attempting to swap the global allocator while other threads are actively allocating or freeing memory results in undefined behavior.

## ABA-Related Function Hooks

The `abafree` and `aba` function pointers in `struct mulle_allocator` provide optional hooks for ABA-problem mitigation systems (such as `mulle-aba`). The library makes no thread-safety claims regarding these hooks. Any safety guarantees must be provided by the external ABA system you plug in.

## Summary

- **Default allocator**: Thread-safe by delegation to libc `malloc`/`free` (defined in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c), lines 34-52)
- **Custom allocators**: Require explicit synchronization by the user; `mulle_allocator` wrappers provide no internal locking
- **Global replacement**: Swapping `mulle_allocator_default` is not thread-safe and must occur during single-threaded initialization
- **ABA hooks**: No thread-safety guarantees; dependent on external ABA implementation
- **Detection**: Use `mulle_allocator_is_stdlib_allocator` (lines 68-73 in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c)) to verify if an allocator uses standard libc functions

## Frequently Asked Questions

### Is mulle_malloc thread-safe?

Yes. When using the default allocator, `mulle_malloc` forwards to the standard C library `malloc`, which is thread-safe on all modern platforms. The default allocator is defined in [`src/mulle-allocator.c`](https://github.com/mulle-c/mulle-allocator/blob/main/src/mulle-allocator.c) and uses the stdlib function pointers directly.

### Do I need to add locks when using a custom allocator with mulle_allocator?

Yes. If your custom allocator is not inherently thread-safe, you must provide your own synchronization (such as mutexes) around the `malloc`, `free`, `calloc`, and `realloc` callbacks. The `mulle_allocator` API functions are thin wrappers that do not perform any internal locking.

### Can I replace the global allocator at runtime safely?

No. Replacing `mulle_allocator_default` at runtime is not thread-safe according to the documentation in [`assets/dox/TOC.md`](https://github.com/mulle-c/mulle-allocator/blob/main/assets/dox/TOC.md). You must perform the replacement during program initialization before spawning any threads that might use the allocator.

### Are the ABA protection hooks in mulle_allocator thread-safe?

No. The `abafree` and `aba` function pointers are optional hooks for external ABA-management systems. The `mulle-allocator` library itself provides no thread-safety guarantees for these callbacks; safety must be implemented by the plugged-in ABA system (such as `mulle-aba`).