Monday, February 6, 2017

Mastering Thread Synchronization in C++ with Practical Examples

Thread synchronization is crucial in multithreaded programming to ensure that multiple threads can cooperate and share resources without conflicts. Here, we explore various synchronization techniques using practical examples in C++.

1. Signaling

Signaling allows one thread to notify another that an event has occurred. This ensures that a specific section of code in one thread runs before a section in another thread.

Example: Ensuring handlerA runs before handlerB.

[code]

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

pthread_t thread_a;
pthread_t thread_b;
sem_t sema;

sem_init(&sema, 0, 0);

void* handlerA(void*) {
    printf("functionA\n");
    sem_post(&sema);
    return NULL;
}

void* handlerB(void*) {
    sem_wait(&sema);
    printf("functionB\n");
    return NULL;
}

int main() {
    pthread_create(&thread_a, NULL, handlerA, NULL);
    pthread_create(&thread_b, NULL, handlerB, NULL);
    pthread_join(thread_a, NULL);
    pthread_join(thread_b, NULL);
    return 0;
}


2. Rendezvous:


Rendezvous extends signaling by ensuring two threads wait for each other at specific points.

Example: Ensuring steps in handlerA and handlerB occur in a specific order.

[code]
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

pthread_t thread_a;
pthread_t thread_b;
sem_t sema;
sem_t semb;

sem_init(&sema, 0, 0);
sem_init(&semb, 0, 0);

void* handlerA(void*) {
    printf("functionA step 1\n");
    sem_post(&sema);
    sem_wait(&semb);
    printf("functionA step 2\n");
    return NULL;
}

void* handlerB(void*) {
    printf("functionB step 1\n");
    sem_post(&semb);
    sem_wait(&sema);
    printf("functionB step 2\n");
    return NULL;
}

int main() {
    pthread_create(&thread_a, NULL, handlerA, NULL);
    pthread_create(&thread_b, NULL, handlerB, NULL);
    pthread_join(thread_a, NULL);
    pthread_join(thread_b, NULL);
    return 0;
}


3. Mutex:

A mutex ensures that only one thread accesses a critical section at a time.

Example: Safely incrementing a shared counter.



[code]
#include <pthread.h>
#include <stdio.h>

pthread_t thread_a;
pthread_t thread_b;
pthread_mutex_t mutex;

int count = 0;

void* handlerA(void*) {
    pthread_mutex_lock(&mutex);
    count++;
    pthread_mutex_unlock(&mutex);
    return NULL;
}

void* handlerB(void*) {
    pthread_mutex_lock(&mutex);
    count++;
    pthread_mutex_unlock(&mutex);
    return NULL;
}

int main() {
    pthread_mutex_init(&mutex, NULL);
    pthread_create(&thread_a, NULL, handlerA, NULL);
    pthread_create(&thread_b, NULL, handlerB, NULL);
    pthread_join(thread_a, NULL);
    pthread_join(thread_b, NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}



4. Multiplex 


Multiplexing allows multiple threads to enter the critical section, controlled by a semaphore initialized to a specific value.

Example: Allowing two threads to run in the critical section simultaneously.


[code]
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

pthread_t thread_a;
pthread_t thread_b;
pthread_t thread_c;
sem_t sema;

sem_init(&sema, 0, 2);

void* handlerA(void*) {
    for(int i = 0; i < 10; i++) {
        sem_wait(&sema);
        printf("functionA\n");
        sem_post(&sema);
    }
    return NULL;
}

void* handlerB(void*) {
    for(int i = 0; i < 10; i++) {
        sem_wait(&sema);
        printf("functionB\n");
        sem_post(&sema);
    }
    return NULL;
}

void* handlerC(void*) {
    for(int i = 0; i < 10; i++) {
        sem_wait(&sema);
        printf("functionC\n");
        sem_post(&sema);
    }
    return NULL;
}

int main() {
    pthread_create(&thread_a, NULL, handlerA, NULL);
    pthread_create(&thread_b, NULL, handlerB, NULL);
    pthread_create(&thread_c, NULL, handlerC, NULL);
    pthread_join(thread_a, NULL);
    pthread_join(thread_b, NULL);
    pthread_join(thread_c, NULL);
    return 0;
}

5. Barrier:

A barrier ensures that no thread proceeds past a certain point until all threads reach that point.

Example: Synchronizing threads before proceeding to the next step.


[code]
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

pthread_t thread_a;
pthread_t thread_b;
pthread_t thread_c;
pthread_mutex_t mutex;
sem_t barrier;
int count = 0;

void* handlerA(void*) {
    pthread_mutex_lock(&mutex);
    count++;
    printf("Func A access done.\n");
    if (count == 3) sem_post(&barrier);
    pthread_mutex_unlock(&mutex);
    sem_wait(&barrier);
    sem_post(&barrier);
    printf("Function A\n");
    return NULL;
}

void* handlerB(void*) {
    pthread_mutex_lock(&mutex);
    count++;
    printf("Func B access done.\n");
    if (count == 3) sem_post(&barrier);
    pthread_mutex_unlock(&mutex);
    sem_wait(&barrier);
    sem_post(&barrier);
    printf("Function B\n");
    return NULL;
}

void* handlerC(void*) {
    pthread_mutex_lock(&mutex);
    count++;
    printf("Func C access done.\n");
    if (count == 3) sem_post(&barrier);
    pthread_mutex_unlock(&mutex);
    sem_wait(&barrier);
    sem_post(&barrier);
    printf("Function C\n");
    return NULL;
}

int main() {
    sem_init(&barrier, 0, 0);
    pthread_create(&thread_a, NULL, handlerA, NULL);
    pthread_create(&thread_b, NULL, handlerB, NULL);
    pthread_create(&thread_c, NULL, handlerC, NULL);
    pthread_join(thread_a, NULL);
    pthread_join(thread_b, NULL);
    pthread_join(thread_c, NULL);
    return 0;
}

6. Deadlock:

Deadlock occurs when two or more threads are waiting on each other to release resources, causing all of them to get stuck.

Example: A simple deadlock scenario.


[code]

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>

pthread_t thread_a;
pthread_t thread_b;
sem_t sema;
sem_t semb;

sem_init(&sema, 0, 0);
sem_init(&semb, 0, 0);

void* handlerA(void*) {
    printf("functionA step 1\n");
    sem_wait(&semb);
    sem_post(&sema);
    printf("functionA step 2\n");
    return NULL;
}

void* handlerB(void*) {
    printf("functionB step 1\n");
    sem_wait(&sema);
    sem_post(&semb);
    printf("functionB step 2\n");
    return NULL;
}

int main() {
    pthread_create(&thread_a, NULL, handlerA, NULL);
    pthread_create(&thread_b, NULL, handlerB, NULL);
    pthread_join(thread_a, NULL);
    pthread_join(thread_b, NULL);
    return 0;
}

7. Producer-consumer:

The producer-consumer problem involves multiple threads producing items and adding them to a buffer, while other threads consume and remove them from the buffer.

Example: Unlimited buffer producer-consumer.


[code]

#include <pthread.h>
#include <semaphore.h>
#include <deque>
#include <stdio.h>

pthread_t thread_producer;
pthread_t thread_consumer;
pthread_mutex_t mutex;
sem_t sema;
std::deque<int> buffer;

pthread_mutex_init(&mutex, NULL);
sem_init(&sema, 0, 0);

void* producer(void*) {
    while(true) {
        int num = rand() % 100;
        printf("producer set %d\t", num);
        pthread_mutex_lock(&mutex);
        buffer.push_front(num);
        sem_post(&sema);
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

void* consumer(void*) {
    while(true) {
        sem_wait(&sema);
        pthread_mutex_lock(&mutex);
        int back = buffer.back();
        buffer.pop_back();
        pthread_mutex_unlock(&mutex);
        printf("consumer get %d\n", back);
    }
    return NULL;
}

int main() {
    pthread_create(&thread_producer, NULL, producer, NULL);
    pthread_create(&thread_consumer, NULL, consumer, NULL);
    pthread_join(thread_producer, NULL);
    pthread_join(thread_consumer, NULL);
    return 0;
}

8. Finite Buffer Producer-consumer:

When the buffer is finite, producers need to wait if the buffer is full, and consumers need to wait if the buffer is empty.

Example: Producer-consumer with finite buffer.

[code]
#include <pthread.h> #include <semaphore.h> #include <deque> #include <stdio.h> #define N 10 pthread_t thread_producer; pthread_t thread_consumer; pthread_mutex_t mutex; sem_t sema; sem_t spaces; std::deque<int> buffer; pthread_mutex_init(&mutex, NULL); sem_init(&sema, 0, 0); sem_init(&spaces, 0, N); void* producer(void*) { while(true) { int num = rand() % 100; printf("producer set %d\t", num); sem_wait(&spaces); // wait signal pthread_mutex_lock(&mutex); buffer.push_front(num); sem_post(&sema); pthread_mutex_unlock(&mutex); } return NULL; } void* consumer(void*) { while(true) { sem_wait(&sema); pthread_mutex_lock(&mutex); int back = buffer.back(); buffer.pop_back(); pthread_mutex_unlock(&mutex); sem_post(&spaces); // signaling producer printf("consumer get %d\n", back); } return NULL; } int main() { pthread_create(&thread_producer, NULL, producer, NULL); pthread_create(&thread_consumer, NULL, consumer, NULL); pthread_join(thread_producer, NULL); pthread_join(thread_consumer, NULL); return 0; }

9. Readers and Writers Problem:

This problem involves readers and writers who need different synchronization. Multiple readers can access the resource simultaneously, but a writer requires exclusive access.

Example: Readers-writers problem.

[code]
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

#define N 4
pthread_t thread_writer[N];
pthread_t thread_reader[N];
int readers = 0;

pthread_mutex_t writeOk;
pthread_mutex_t readerOn;

char BufferA[20];
char BufferB[20];

void* writer(void*) {
    int counter = 1;
    while(1){
        pthread_mutex_lock(&writeOk);
        sprintf(BufferA, "WriteA: %d", counter);
        usleep(100);
        sprintf(BufferB, "WriteB: %d", counter);
        counter++;
        pthread_mutex_unlock(&writeOk);
    }
    return NULL;
}

void* reader(void* arg) {
    while(1) {
        pthread_mutex_lock(&readerOn);
        readers++;
        if (readers == 1) {
            pthread_mutex_lock(&writeOk);
        }
        pthread_mutex_unlock(&readerOn);

        printf("%s from thread %ld\n", BufferA, (long)arg);
        printf("%s from thread %ld\n", BufferB, (long)arg);
        usleep(500);

        pthread_mutex_lock(&readerOn);
        readers--;
        if(readers == 0) {
            pthread_mutex_unlock(&writeOk);
        }
        pthread_mutex_unlock(&readerOn);
    }
    return NULL;
}

int main() {
    pthread_mutex_init(&writeOk, NULL);
    pthread_mutex_init(&readerOn, NULL);

    for(int i = 0; i < N; i++) {
        pthread_create(&thread_writer[i], NULL, writer, NULL);
        pthread_create(&thread_reader[i], NULL, reader, (void*)(long)i);
    }

    for(int i = 0; i < N; i++) {
        pthread_join(thread_writer[i], NULL);
        pthread_join(thread_reader[i], NULL);
    }

    pthread_mutex_destroy(&writeOk);
    pthread_mutex_destroy(&readerOn);

    return 0;
}
By understanding and implementing these synchronization techniques, you can effectively manage multithreaded programs, ensuring safe and efficient execution of concurrent tasks.


No comments:

Post a Comment