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:
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;
}
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.
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;
}
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.
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;
}
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.
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;
}
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.
#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