Synchronization

Race Conditions (1)

Suppose inc() is executed by at least two threads in parallel:

Very bad code
static int global;

void inc()
{
    global++;
}

CPU A

CPU B

Memory

Instr

Loc

Instr

Loc

Glob

load

42

42

42

load

42

42

inc

43

42

43

inc

43

42

43

store

43

43

store

43

43

43

  • The variable global has seen only one increment!!

  • Load/Modify/Store Conflict

  • The most basic race condition

Race Conditions (2)

Imagine more complex data structures (linked lists, trees): if incrementing a dumb integer bears a race condition, then what can we expect in a multithreaded world?

  • No single data structure of C++’s Standard Template Library is thread safe

  • std::string’s copy constructor and assignment operator are thread safe (GCC’s Standard C++ Librarynot by standard)

  • std::string’s other methods are not thread safe

  • stdio and iostream are thread safe (by standard since C++11)

Mutex (1)

int pthread_mutex_init(pthread_mutex_t *mutex,
       const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • Dynamic initialization using pthread_mutex_init()/``pthread_mutex_destroy()

  • attr == NULL ⟶ default mutex (⟶ later)

  • Static initialization using PTHREAD_MUTEX_INITIALIZER

Mutex (2)

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • Simple lock/unlock must be enough

  • If you find yourself using “trylock”, then something’s wrong*

  • Polling is never right!

Mutex (3)

Better code
static pthread_mutex_t global_mutex =
    PTHREAD_MUTEX_INITIALIZER;
static int global;

void inc()
{
    /* error handling omitted */
    pthread_mutex_lock(&global_mutex);
    global++;
    pthread_mutex_unlock(&global_mutex);
}

Mutex Types

int pthread_mutexattr_settype(
    pthread_mutexattr_t *attr, int type);
  • PTHREAD_MUTEX_NORMAL: no checks, no nothing. Same thread locks mutex twice in a row before unlock ⟶ Deadlock.

  • PTHREAD_MUTEX_ERRORCHECK: Deadlock check; unlocking a mutex locked by another thread ⟶ Error

  • PTHREAD_MUTEX_RECURSIVE: owner can lock same mutex twice

  • PTHREAD_MUTEX_DEFAULTPTHREAD_MUTEX_NORMAL

Atomic Instructions

Simple integers don’t need a mutex

fetch_and_add()
static int global;

void inc()
{
    __sync_fetch_and_add(&global, 1);
}

More ⟶ info gcc, GCC manual