在编写并发程序时,如何有效地管理线程间的同步与互斥,是确保程序正确性和性能的关键。C语言标准库本身并不直接提供高级的多线程编程接口,但POSIX线程(pthread)库作为在UNIX及类UNIX系统上广泛使用的线程库,为C语言程序提供了丰富的线程同步机制,包括互斥量(Mutexes)、条件变量(Condition Variables)、信号量(Semaphores)等。本章将重点介绍如何使用这些技术,特别是互斥量,来协调线程的运行。
13.1.1 线程同步的概念
线程同步是指多个线程在执行过程中,按照一定的顺序或条件相互协作,以完成共同的任务。它确保了在多线程环境下,数据的访问和修改是安全的,避免了数据竞争(Race Conditions)和死锁(Deadlocks)等问题。
13.1.2 互斥量的作用
互斥量(Mutex,Mutual Exclusion的缩写)是一种用于保护共享资源的数据结构,它确保在任何时刻,只有一个线程可以访问该资源。当一个线程想要访问被互斥量保护的资源时,它必须先锁定该互斥量;访问结束后,再解锁互斥量,以便其他线程可以访问。
在POSIX线程库中,互斥量的相关操作主要通过以下几个函数实现:
pthread_mutex_init()
:初始化互斥量。pthread_mutex_lock()
:尝试锁定互斥量。如果互斥量已被锁定,则调用线程将被阻塞,直到互斥量被解锁。pthread_mutex_unlock()
:解锁互斥量,允许其他线程访问被保护的资源。pthread_mutex_destroy()
:销毁互斥量,释放其占用的资源。示例代码:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t lock;
int counter = 0;
void* increment(void* arg) {
pthread_mutex_lock(&lock);
counter++;
printf("Counter = %d\n", counter);
pthread_mutex_unlock(&lock);
return NULL;
}
int main() {
pthread_t t1, t2;
pthread_mutex_init(&lock, NULL); // 初始化互斥量
pthread_create(&t1, NULL, increment, NULL);
pthread_create(&t2, NULL, increment, NULL);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
pthread_mutex_destroy(&lock); // 销毁互斥量
return 0;
}
在上述示例中,两个线程都试图增加同一个全局变量counter
的值。通过使用互斥量lock
,我们确保了每次只有一个线程能访问并修改counter
,从而避免了数据竞争。
POSIX线程库允许通过pthread_mutexattr_t
结构体和相关的函数来设置互斥量的属性,如类型(如快速互斥量、递归互斥量等)。
设置互斥量属性示例:
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&lock, &attr);
pthread_mutexattr_destroy(&attr);
条件变量(Condition Variables)通常与互斥量一起使用,以等待或通知线程某些条件的发生。条件变量允许线程挂起(阻塞)在一个条件上,直到另一个线程修改了条件并通知它。
关键函数:
pthread_cond_init()
:初始化条件变量。pthread_cond_wait()
:在调用线程上阻塞,等待条件变量的条件为真,同时释放与之关联的互斥量。pthread_cond_signal()
:唤醒等待该条件变量的一个线程。pthread_cond_broadcast()
:唤醒等待该条件变量的所有线程。示例:生产者-消费者问题(简化版)
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int buffer = 0;
int full = 0;
void* producer(void* arg) {
while (1) {
pthread_mutex_lock(&mutex);
while (full) {
pthread_cond_wait(&cond, &mutex); // 等待缓冲区不满
}
buffer = 1; // 生产数据
full = 1;
printf("Produced\n");
pthread_cond_signal(&cond); // 通知消费者
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟耗时操作
}
return NULL;
}
void* consumer(void* arg) {
while (1) {
pthread_mutex_lock(&mutex);
while (!full) {
pthread_cond_wait(&cond, &mutex); // 等待缓冲区有数据
}
buffer = 0; // 消费数据
full = 0;
printf("Consumed\n");
pthread_cond_signal(&cond); // 可选,如果多个消费者可能需要
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟耗时操作
}
return NULL;
}
int main() {
pthread_t prod, cons;
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL); // 实际应用中,通常不会在这里等待终止
return 0;
}
在这个示例中,生产者和消费者通过互斥量和条件变量协同工作,确保了数据的正确生产和消费,避免了数据竞争和死锁的问题。
通过以上内容的介绍,您应该已经对如何在C语言中使用POSIX线程库中的互斥量等技术来协调线程运行有了较为全面的了解。在实际编程中,根据具体需求选择合适的同步机制,并遵循最佳实践,是编写高效、稳定并发程序的关键。