Implementation Methods of Thread Synchronization Mechanisms in C Language

Implementation Methods of Thread Synchronization Mechanisms in C Language

In multithreaded programming, synchronization between threads is an important issue. When multiple threads access shared resources simultaneously, the absence of appropriate synchronization mechanisms can lead to data inconsistency or program crashes. This article will introduce several common thread synchronization mechanisms in C language, including mutexes, condition variables, and spinlocks, along with corresponding code examples.

1. Mutex

A mutex is one of the most basic synchronization mechanisms used to protect shared resources. At any given time, only one thread is allowed to access the protected data.

Example Code

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREADS 5
pthread_mutex_t mutex;
int shared_data = 0;
void* thread_function(void* arg) {
    pthread_mutex_lock(&mutex); // Lock
    int thread_id = *((int*)arg);
    // Simulate operation on shared data
    printf("Thread %d: Current shared data: %d\n", thread_id, shared_data);
    shared_data++;
    pthread_mutex_unlock(&mutex); // Unlock
    return NULL;
}
int main() {
    pthread_t threads[NUM_THREADS];
    int thread_ids[NUM_THREADS];
    pthread_mutex_init(&mutex, NULL); // Initialize mutex
    for (int i = 0; i < NUM_THREADS; i++) {
        thread_ids[i] = i + 1;
        pthread_create(&threads[i], NULL, thread_function, &thread_ids[i]);
    }
    for (int i = 0; i < NUM_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    printf("Final shared data: %d\n", shared_data);
    pthread_mutex_destroy(&mutex); // Destroy mutex
    return 0;
}

Program Explanation

  • <span>pthread_mutex_lock</span> and <span>pthread_mutex_unlock</span> are used for locking and unlocking.
  • In each thread, we first acquire the mutex, then safely modify the shared data.
  • Finally, the created mutex is destroyed to free resources.

2. Condition Variable

A condition variable is used for signaling between threads. When a certain condition is met, one or more threads waiting for that condition can be awakened.

Example Code

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define MAX_BUFFER_SIZE 5
pthread_cond_t cond_var;
pthread_mutex_t mutex;
int buffer[MAX_BUFFER_SIZE];
int count = 0;
void* producer(void* arg) {
   for (int i = 0; i < MAX_BUFFER_SIZE; ++i) {
       pthread_mutex_lock(&mutex);
       while (count == MAX_BUFFER_SIZE) {
            pthread_cond_wait(&cond_var, &mutex);
        }
       buffer[count++] = i;
       printf("Produced: %d\n", i);
       pthread_cond_signal(&cond_var);
       pthread_mutex_unlock(&mutex);
   }
   return NULL;
}
void* consumer(void* arg) {
   for (int i = 0; i < MAX_BUFFER_SIZE; ++i) {
       pthread_mutex_lock(&mutex);
       while (count == 0) {
            pthread_cond_wait(&cond_var, &mutex);
        }
       int item = buffer[--count];
       printf("Consumed: %d\n", item);
       pthread_cond_signal(&cond_var);
       pthread_mutex_unlock(&mutex);
   }
   return NULL;
}
int main() {
   pthread_t prod_thread, cons_thread;
   pthread_cond_init(&cond_var, NULL);
   pthread_mutex_init(&mutex, NULL);
   pthread_create(&prod_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(&mutex);
   return EXIT_SUCCESS;
}

Program Explanation

  • In the producer function, when the buffer is full, <span>pthread_cond_wait</span> is called to wait for the consumer to consume data.
  • The consumer function waits for the producer to produce data when the buffer is empty.
  • Using <span>pthread_cond_signal</span> to wake up the waiting thread.

3. Spinlock

A spinlock is a lightweight synchronization primitive that continuously loops to acquire the lock when it cannot be obtained. This method is suitable for small critical sections that need to hold exclusive rights for a short time, but if held for a longer time, it can waste CPU time.

Example Code

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
typedef struct spinlock_t {
     volatile int lock_flag;
} spinlock_t;
void spinlock_init(spinlock_t *s) { s->lock_flag=0;}
void spinlock_acquire(spinlock_t *s){
     while (__sync_lock_test_and_set(&(s->lock_flag),1)) {}
}
void spinlock_release(spinlock_t *s){
     __sync_lock_release(&(s->lock_flag));
}
spinlock_t my_spinlock;
void* worker(void* arg){
     spinlock_acquire (&my_spinlock );
     printf ("Thread %ld acquired the lock.\n", (long)arg );
     sleep(1 ); // Simulate work.
     printf ("Thread %ld releasing the lock.\n",(long)arg );
     spinlock_release (&my_spinlock );
     return(NULL );
}
int main(){
      const int num_threads=5 ;
      long t ;
      pthread_t t[num_threads];
      spinlock_init (&my_spinlock );
      for(t=0 ;t<num_threads ;t++){
          pthread_create (&t[t],NULL ,worker,( void *)t );
      }
      for(t=0 ;t<num_threads ;t++){
          pthread_join(t[t],NULL ) ;
      }
      return(EXIT_SUCCESS ) ;
}

Program Explanation

  • The spinlock controls access to the critical section through atomic operations.
  • When a thread cannot acquire the spinlock, it continuously checks and tries to acquire it, avoiding the overhead of context switching, but it may also lead to increased CPU usage.

Conclusion

This article introduced three common methods for implementing multithreaded synchronization in C language: mutexes, condition variables, and spinlocks. Each method has its applicable scenarios, and developers can choose the appropriate method based on specific needs to ensure data consistency and safety in a multithreaded environment. In practical applications, performance and complexity factors should also be considered to achieve the best results.

Leave a Comment