多线程编程,经常会遇到线程直接数据同步,为了保证数据访问安全,就必须考虑线程之间的同步问题。在C语言中,多线程编程的线程同步主要依赖于POSIX线程(Pthreads)库提供的同步原语。以下是一些关键的线程同步机制:
互斥锁 (Mutexes)
pthread_mutex_t
是一种互斥对象,用于保护共享资源,确保同一时间只有一个线程可以访问。pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
pthread_mutex_lock(&mutex); // 上锁
// 访问共享资源的代码
pthread_mutex_unlock(&mutex); // 解锁
读写锁 (Read-Write Locks)
pthread_rwlock_t
允许多个线程同时进行读取操作,但在写入操作时会排斥所有其他读写线程。pthread_rwlock_t rwlock;
pthread_rwlock_init(&rwlock, NULL);
pthread_rwlock_rdlock(&rwlock); // 读取锁定
// 读取共享资源的代码
pthread_rwlock_unlock(&rwlock); // 释放锁定
pthread_rwlock_wrlock(&rwlock); // 写入锁定
// 更新共享资源的代码
pthread_rwlock_unlock(&rwlock); // 释放锁定
条件变量 (Condition Variables)
pthread_cond_t
与互斥锁结合使用,允许线程阻塞等待特定条件变为真。pthread_cond_t cond;
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(&cond, NULL);
pthread_mutex_lock(&mutex);
while (!condition_is_true()) { // 检查条件
pthread_cond_wait(&cond, &mutex); // 条件不满足则等待
}
// 当条件满足时执行相关操作
pthread_mutex_unlock(&mutex);
// 另一个线程改变条件后,可以唤醒等待的线程
pthread_mutex_lock(&mutex);
condition_make_true(); // 改变条件
pthread_cond_signal(&cond); // 唤醒一个等待线程
// 或者唤醒所有等待线程
pthread_cond_broadcast(&cond);
pthread_mutex_unlock(&mutex);
信号量 (Semaphores)
sem_t
也可以用于同步线程,它是一个计数器,控制可以同时访问资源的线程数量。sem_t semaphore;
sem_init(&semaphore, 0, 1); // 初始化为1,即一次只允许一个线程通过
sem_wait(&semaphore); // 阻塞直到信号量计数大于0并减一
// 执行临界区代码
sem_post(&semaphore); // 信号量加一,释放资源
以下是一个使用互斥锁(Mutexes)和条件变量(Condition Variables)实现的简单生产者-消费者问题的例子。生产者线程生成数据并放入缓冲区,消费者线程从缓冲区取出数据处理。
#include <pthread.h>
#include <stdio.h>
#define BUFFER_SIZE 10
int buffer[BUFFER_SIZE];
int buffer_count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t full = PTHREAD_COND_INITIALIZER;
pthread_cond_t empty = PTHREAD_COND_INITIALIZER;
void* producer(void* arg) {
int i;
for (i = 0; i < 20; i++) {
pthread_mutex_lock(&mutex);
while (buffer_count == BUFFER_SIZE) { // 如果缓冲区已满
pthread_cond_wait(&full, &mutex); // 生产者等待
}
buffer[buffer_count++] = i; // 放入数据
printf("Producer produced %d\n", i);
pthread_cond_signal(&empty); // 唤醒消费者
pthread_mutex_unlock(&mutex);
}
return NULL;
}
void* consumer(void* arg) {
int i;
while (1) {
pthread_mutex_lock(&mutex);
while (buffer_count == 0) { // 如果缓冲区为空
pthread_cond_wait(&empty, &mutex); // 消费者等待
}
int data = buffer[--buffer_count]; // 取出数据
printf("Consumer consumed %d\n", data);
pthread_cond_signal(&full); // 唤醒生产者
pthread_mutex_unlock(&mutex);
// 在这里可以处理消费的数据,这里为了简化直接输出
}
return NULL;
}
int main() {
pthread_t producer_thread, consumer_thread;
pthread_create(&producer_thread, NULL, producer, NULL);
pthread_create(&consumer_thread, NULL, consumer, NULL);
pthread_join(producer_thread, NULL);
pthread_join(consumer_thread, NULL);
pthread_cond_destroy(&full);
pthread_cond_destroy(&empty);
pthread_mutex_destroy(&mutex);
return 0;
}
在这个例子中,创建了两个线程:一个生产者线程和一个消费者线程。生产者在缓冲区未满时产生数据,并通过条件变量full
通知消费者;消费者在缓冲区非空时消费数据,并通过条件变量empty
通知生产者。通过互斥锁mutex
保护对共享资源(缓冲区和缓冲区计数器)的访问,确保了线程间的同步。
在使用这些同步机制时,重要的是要理解它们各自的适用场景和潜在的开销。不恰当的同步可能导致性能下降或死锁等问题。