Re entrancy vs thread safety

This is part of the Semicolon&Sons Code Diary - consisting of lessons learned on the job. You're in the concurrency category.

Last Updated: 2024-03-02

Thread Safety

Thread-safety have the lowest risk of data corruption. A thread-safe function can be called simultaneously from multiple threads, even when the invocations use shared data, either because it uses thread local variables or because all references to the shared data are serialized (e.g. with a mutex). This prevents dangerous situations where two threads might simultaneously modify the same data, leaving it corrupt.

Reentrantcy

A reentrant function can also be called simultaneously from multiple threads, but only if each invocation uses its own data. In object-oriented code, this means re-entrant code will work if it has different instances of the class in each thread running simultaneously (but not if a single instance was shared between them - that would require thread safety)

Comparison

Hence, a thread-safe function is always reentrant, but a reentrant function is not always thread-safe.

The following is re-entrant but not thread-safe (since if thread A and B read old value from CPU register at same time, then both incremented, we'd only increment once instead of twice)

// Re-entrant if one class per thread
class Counter
{
public:
    Counter() { n = 0; }

    void increment() { ++n; }
    void decrement() { --n; }
    int value() const { return n; }

private:
    int n;
};

To make it thread-safe we'd do this

class Counter
{
public:
    Counter() { n = 0; }

    void increment() { QMutexLocker locker(&mutex); ++n; }
    void decrement() { QMutexLocker locker(&mutex); --n; }
    int value() const { QMutexLocker locker(&mutex); return n; }

private:
    mutable QMutex mutex;
    int n;
};

The QMutexLocker class automatically locks the mutex in its constructor and unlocks it when the destructor is invoked, at the end of the function. Locking the mutex ensures that access from different threads will be serialized