Thread Synchronization and Mutex Mechanisms in C Language

Thread Synchronization and Mutex Mechanisms in C Language

In multithreaded programming, multiple threads may access shared resources simultaneously, which can lead to data inconsistency and unpredictable program behavior. To avoid these issues, we need to use thread synchronization and mutex mechanisms. This article will detail several commonly used synchronization and mutex mechanisms in C language, along with code examples for demonstration.

1. Basics of Threads

In C language, creating and managing threads is typically done using the POSIX Threads (pthread) library. First, we need to include the appropriate header files:

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

2. Mutex

2.1 What is a Mutex?

A mutex is a mechanism used to protect shared resources, ensuring that only one thread can access the resource at a time. When a thread acquires the lock, other threads attempting to acquire the same lock will be blocked until the thread holding the lock releases it.

2.2 Using Mutex

Below is a simple example demonstrating how to use a mutex to protect a shared variable:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define NUM_THREADS 5
int counter = 0; // Global counter
pthread_mutex_t lock; // Define a mutex

void* increment_counter(void* threadid) {
    long tid = (long)threadid;
    pthread_mutex_lock(&lock); // Lock
    for (int i = 0; i < 100000; i++) {
        counter++; // Modify shared variable
    }
    printf("Thread %ld: Counter = %d\n", tid, counter);
    pthread_mutex_unlock(&lock); // Unlock
    pthread_exit(NULL);
}

int main() {
    pthread_t threads[NUM_THREADS];
    pthread_mutex_init(&lock, NULL); // Initialize mutex
    for (long t = 0; t < NUM_THREADS; t++) {
        pthread_create(&threads[t], NULL, increment_counter, (void*)t);
    }
    for (int t = 0; t < NUM_THREADS; t++) {
        pthread_join(threads[t], NULL);
    }
    printf("Final Counter Value: %d\n", counter);
    pthread_mutex_destroy(&lock); // Destroy mutex
    return EXIT_SUCCESS;
}

Example Explanation:

  • We defined a global counter <span>counter</span> and a <span>pthread_mutex_t</span> type variable <span>lock</span>.
  • In each thread, we first call <span>pthread_mutex_lock()</span> to lock, then modify the counter, and finally call <span>pthread_mutex_unlock()</span> to unlock.
  • The main function creates multiple threads and waits for them to complete execution.
  • The final output of the counter value shows that due to the use of mutex, the synchronization operation ensures data consistency.

3. Condition Variables

3.1 What is a Condition Variable?

A condition variable is a method for signaling between threads, allowing one or more threads to wait under certain conditions while other threads can notify these waiting threads to continue execution.

3.2 Using Condition Variables

Below is an example of using condition variables to implement a producer-consumer model:

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#define BUFFER_SIZE 5
int buffer[BUFFER_SIZE];
int count = 0;
pthread_cond_t cond_var;
pthread_mutex_t lock;

void* producer(void* arg) {
   for (int i = 0; i < BUFFER_SIZE * 2; i++) {
       pthread_mutex_lock(&lock);
       while (count == BUFFER_SIZE) {
            pthread_cond_wait(&cond_var, &lock);
        }
       buffer[count++] = i;
       printf("Produced: %d\n", i);
       pthread_cond_signal(&cond_var);
       pthread_mutex_unlock(&lock);
   }
   return NULL;
}

void* consumer(void* arg) {
   for (int i = 0; i < BUFFER_SIZE * 2; i++) {
       pthread_mutex_lock(&lock);
       while (count == 0) {
            pthread_cond_wait(&cond_var, &lock);
        }
       int item = buffer[--count];
       printf("Consumed: %d\n", item);
       pthread_cond_signal(&cond_var);
       pthread_mutex_unlock(&lock);
   }
   return NULL;
}

int main() {
   pthread_t prod_thread, cons_thread;
   pthread_cond_init(&cond_var, NULL);
   pthread_mutex_init(&lock, NULL);
   pthread_create(∏_thread, NULL, producer, NULL);
   pthread_create(&cons_thread, NULL, consumer, NULL);
   pthread_join(prod_thread,NULL);
   pthread_join(cons_thread,NULL);
   pthread_cond_destroy(&cond_var);
   pthread_mutex_destroy(&lock);
   return EXIT_SUCCESS;
}

Example Explanation:

  • We defined a buffer and related data structures, including producer and consumer functions.
  • In the producer function, if the buffer is full, it calls <span>pthread_cond_wait()</span> to wait; if there is space, it adds a new item and notifies the consumer.
  • The consumer function is similar; if the buffer is empty, it waits, and when there are items, it consumes and notifies the producer.

In this way, we can effectively coordinate access to the shared resource (buffer) between two different roles, thus avoiding race conditions.

Conclusion

This article introduced the basic synchronization and mutex mechanisms in C language, including how to use mutex to protect shared data and how to utilize condition variable for more complex data interchange processing. These tools are essential for building safe and efficient multithreaded applications. In practical development, it is crucial to choose the appropriate method based on specific needs to ensure stable program operation.

Leave a Comment