Synchronization & Deadlock

677 阅读3分钟

Part 1: Mutex Locks

Critical Section

When thread process the variable. There are three steps:

  • 1 Copy the variable from memory to CPU
  • 2 Do some calculation
  • 3 Store the new value back to memory

If multiple thread is executing same variable at the same time, there might be a critical section.

mutex_lock

We can use pthread_mutex_lock() to avoid the critical section.

    pthread_mutex_t *lock = malloc(sizeof(pthread_mutex_t);
    pthread_mutex_init(lock,NULL);
    //same as pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER
    pthread_mutex_lock(lock);
    
    pthread_mutex_unlock(lock);
    pthread_mutex_destroy(lock);
    free(lock);

Pthread_mutex_lock won't stop other threads, other threads will have to wait when they are trying to lock the same lock until the mutex is unlocked. If a mutex was locked, it can't be unlock by another thread.

Gotchas

  • Multiple threads init/destroy has undefined behavior
  • Destroying a locked mutex has undefined behavior
  • Forgot to unlock will lead to deadlock
  • Can't copy pthread_mutex_t to a new memory location
  • Use uninitialized mutex
  • Not calling destroy lead to resource leak
  • Locking/ unlocking wrong mutex

Part 2: Counting Semaphores

    sem_t s;
    sem_init(&s, 0, 10);
    sem_wait(&s);
    sem_post(&s);
    sem_destroy(&s);

Thread Safe Push:

   pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
   int count = 0;
   double values[10];
   sem_t sitems, sremain;
   
   void init(){
        sem_init(&sitems, 0, 0);
        sem_init(&sremains, 0, 10);
    }
    
    double pop(){
        sem_wait(&sitems);
        pthread_mutex_lock(&m);
        double result = values[--count];
        pthread_mutex_unlock(&m);
        sem_post(&sremains);
        return result;
    }
    
    void push(double v){
        sem_wait(&sremains);
        pthread_mutex_lock(&m);
        values[count++] = v;
        pthread_mutex_unlock(&m);
        sem_post(&sitems);
    }
   }

Three desired properties for solutions to the critical section problem

  • Mutual Exculsion: Only one thread/process get access
  • Progress: If there is no progress inside critical section, thread should be able to get inside without waiting
  • Bounded: The wait time should be finite

The Critical Section Problem

raise my flag
turn = your_id
wait while your flag is raised and turn is your_id
//Do critical Section
lower my flag

Part 3: Condition Variables

What is condition variable

It allow threads sleep until tickled. We can use pthread_cond_signal() to wake up one thread (System decide which one to wake up) or all threads with pthread_cond_broadcast(). There will be a spurious wake of waiting thread.

  • spurious wake exist for performance.

pthread_cond_wait

  1. unlock the mutex
  2. wait until pthread_cond_signal is called
  3. lock before return

Use condition number to write semaphore

    define struct sem_t{
        int count;
        pthread_mutex_t m;
        pthread_cond_t cv;
    } sem_t
    int sem_init(sem_t *s, int pshared, int value){
        if(pshared){errno = ENOSYS;return -1;}
        s->count = 0;
        pthread_mutex_init(s->&m, NULL);
        pthread_cond_init(s->&cv, NULL);
        return 0;
    }
    void sem_wait(sem_t *s){
        pthread_mutex_lock(s->&m);
        while(s->count == 0){
            pthread_cond_wait(&cv,&m);
        }
        s->count--;
        pthread_mutex_unlock(s->&m);
    }
    void sem_signal(){
        pthread_mutex_lock(s->&m);
        count++;
        pthread_cond_signal(s->&cv);
        pthread_mutex_unlock(s->&m);
    }
    

Part 4: Barrier

Barrier is used to wait N threads to reach a certain point before continuing onto next step. There is a function named pthread_barrier_wait(). We need to declare a pthread_barrier_t variable and initialize it with pthread_barrier_init().

Write our own simple barrier:

pthread_mutex_lock(&m);
remain --;
if(remain == 0){
    pthread_cond_broadcast(&cv);
}
while(remain > 0){
    pthread_cond_wait(&cv,&m);
}
pthread_mutex_unlock(&m);

Part 5: Reader/Writer problen

reader(){
    lock(&m);
    while(writer) pthread_cond_wait(&cv, &m);
    read++
    unlock(&m);
    
    lock(&m)
    read--;
    pthread_cond_broadcast(&cv);
    unlock(&m);
}
writer(){
    lock(&m);
    writer++;
    while(writer || reader) pthread_cond_wait(&cv,&m);
    writer++;
    unlock(&m);
    
    lock(&m);
    writer--;
    writer--;
    pthread_cond_broadcast(&cv);
    unlock(&m);
}

Deadlcok

Coffman conditions

  • Mutex Exclusion: The resource cannot be shared
  • Circular Wait: There exists a cycle in the Resource Allocation Graph
  • Hold and Wait: Holding some resource and waiting for the resource holded by others
  • No pre-emption: The resource can't be taken away by other thread

Dining Philosophers

  • Left-right Deadlock will lead to circle wait. Hold left and wait for right.
  • Trylock? will cause a livelock.
    1. Arbitrator 2. Leave table 3. All choose lower

Exam Practice

1. Define circular wait, mutual exclusion, hold and wait, and no-preemption. How are these related to deadlock?

circular wait: There exist a circle in Resource Allocation Graph. hold and wait: Thread will holding the current resource while waiting for the resource hold by others mutual exclusion: The resource can't be shared no-preemption: Resource can't be taken by others.

2. What problem does the Banker's Algorithm solve?

Deadlock

3. What is the difference between Deadlock Prevention, Deadlock Detection and Deadlock Avoidance?

4. Sketch how to use condition-variable based barrier to ensure your main game loop does not start until the audio and graphic threads have initialized the hardware and are ready.

5. Implement a producer-consumer fixed sized array using condition variables and mutex lock.

6. Create an incorrect solution to the CSP for 2 processes that breaks: i) Mutual exclusion. ii) Bounded wait.

7. Create a reader-writer implementation that suffers from a subtle problem. Explain your subtle bug.