Примитивы синхронизации

Содержание

Введение

volatile

Квалификатор volatile сообщает компилятору, что выражение не нужно оптимизровать. Большинство компиляторов могут автоматически оптимизировать некоторые выражения если сочтут что значение выражения не изменяется и поэтому нет смысла перепроверять его при каждом обращении. Кроме того, компиляторы могут изменять порядок вычисления выражения при компиляции.

Использование в качестве примитва синхронизации может являться безопасным только в том случае, если запись и чтение для выражения аннотированного данным кфалификатором производится атомарно.

/*
cc -std=c11 -pthread volatile.c -o volatile 
*/
#include <stdio.h>
#include <pthread.h>

#define NUM 5

volatile int is_started = 0;

void* thread_function(void* num){
    int thread_num = (int) num;

    while(is_started == 0);

    printf("Thread %d is started\n", thread_num);

    for(int i; i < 1000000; i++);

    return NULL;
}

int main(){
    pthread_t threads[NUM];

    for(int i=0; i < NUM; i++){
        pthread_create(&threads[i], NULL, thread_function, (void *) i);
    }

    printf("Wait to start...\n");
    sleep(1);

    printf("Now go!\n");
    is_started = 1;

    // wait to terminate
    for(int i=0; i < NUM; i++){
        pthread_join(threads[i], NULL);
    }

    return 0;
}

Спин блокировка (spin lock)

Спин-блокировка - циклическая блокировка или блокировка активного ожидания, основанная (аналогично приведенному выше примеру с volatile) на постоянном тестировании переменной блокирования, до момента ее "освобождения".

Реализуется данный вид блокировки на основе атомарных операций в качестве которых выступают асемлерные инструкции имеющиеся у процессора, как например cmpxchg "сравнение с обменом" для x86.

Основным достоинстовом и одновременно недостатком спин-блокировки является отсутствие переключения контекста выполнения, однако при этом происходит активное использование процессора что не всегда оправдано.

/*
cc -g -std=gnu11 -pthread spin_lock.c -o spin_lock 
*/
#include <stdio.h>
#include <pthread.h>

#define NUM 5

pthread_spinlock_t is_started;

void* thread_function(void* num){
    int thread_num = (int) num;

    pthread_spin_lock(&is_started);

    printf("Thread %d is started\n", thread_num);

    for(int i = 0; i < 1000000; i++);

    pthread_spin_unlock(&is_started);
    return NULL;
}

int main(){
    pthread_t threads[NUM];

    pthread_spin_init(&is_started, 0);
    pthread_spin_lock(&is_started);

    for(int i=0; i < NUM; i++){
        pthread_create(&threads[i], NULL, thread_function, (void *) i);
    }

    printf("Wait to start...\n");
    sleep(1);

    printf("Now go!\n");
    pthread_spin_unlock(&is_started);

    // wait to terminate
    for(int i=0; i<NUM; i++){
        pthread_join(threads[i], NULL);
    }

    pthread_spin_destroy(&is_started);
    return 0;
}

Семафор (semaphore)

Семафор - объект, ограничивающий количество потоков, которые могут войти в заданный участок кода (определение введено Эдсгером Дейкстрой).

На текущий момент реализуется на основе фьютекса.

/*
cc -std=c11 -pthread semaphore.c -o semaphore 
*/
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

#define NUM 5

sem_t is_started;

void* thread_function(void* num){
    int thread_num = (int) num;

    sem_wait(&is_started);

    printf("Thread %d is started\n", thread_num);

    for(int i=0; i < 100000000; i++);

    return NULL;
}

int main(){
    pthread_t threads[NUM];

    sem_init(&is_started, 0, 0);

    for(int i=0; i < NUM; i++){
        pthread_create(&threads[i], NULL, thread_function, (void *) i);
    }

    printf("Wait to start...\n");
    sleep(1);

    printf("Now go!\n");
    for(int i=0; i < NUM; i++){
        sem_post(&is_started);
    }

    // wait to terminate
    for(int i=0; i < NUM; i++){
        pthread_join(threads[i], NULL);
    }

    return 0;
}

Мьютекс (mutex)

Мьютекс (mutex, от mutual exclusion — "взаимное исключение")

/*
cc -std=c11 -pthread mutex.c -o mutex 
*/
#include <stdio.h>
#include <pthread.h>

#define NUM 5

pthread_mutex_t is_started;

void* thread_function(void* num){
    int thread_num = (int) num;

    pthread_mutex_lock(&is_started);

    printf("Thread %d is started\n", thread_num);

    for(int i; i < 100000000; i++);

    pthread_mutex_unlock(&is_started);
    return NULL;
}

int main(){
    pthread_t threads[NUM];

    pthread_mutex_init(&is_started, NULL);
    pthread_mutex_lock(&is_started);

    for(int i=0; i<NUM; i++){
        pthread_create(&threads[i], NULL, thread_function, (void *) i);
    }

    printf("Wait to start...\n");
    sleep(1);

    printf("Now go!\n");
    pthread_mutex_unlock(&is_started);

    // wait to terminate
    for(int i=0; i<NUM; i++){
        pthread_join(threads[i], NULL);
    }

    return 0;
}

Фьютекс (futex)

Фьютекс (futex - сокращение от англ. fast userspace mutex).

/*
cc -std=c11 -pthread futex.c -o futex 
*/
#include <stdio.h>
#include <<pthread.h>
#include <linux/futex.h>
#include <syscall.h>

#define NUM 5

int is_started = 0;

int futex_wait(int *uaddr, int val) {
   return syscall(SYS_futex, uaddr, FUTEX_WAIT, val, NULL, NULL, 0);
}

int futex_wake(int *uaddr, int val) {
   return syscall(SYS_futex, uaddr, FUTEX_WAKE, val, NULL, NULL, 0);
}

void* thread_function(void* num){
    int thread_num = (int) num;

    futex_wait(&is_started, 0);

    printf("Thread %d is started\n", thread_num);

    for(int i; i < 100000000; i++);

    return NULL;
}

int main(){
    pthread_t threads[NUM];

    for(int i=0; i < NUM; i++){
        pthread_create(&threads[i], NULL, thread_function, (void *) i);
    }

    printf("Wait to start...\n");
    sleep(1);

    printf("Now go!\n");

    futex_wake(&is_started, NUM);

    // wait to terminate
    for (int i=0; i<NUM; i++){
        pthread_join(threads[i], NULL);
    }

    return 0;
}

Ссылки

Архив