C 语言信号量
外观
信号量(Semaphore)是C语言多线程编程中用于控制资源访问的同步机制,由荷兰计算机科学家艾兹赫尔·戴克斯特拉于1965年提出。它通过计数器管理对共享资源的访问权限,解决多线程环境下的竞态条件问题。
基本概念[编辑 | 编辑源代码]
信号量本质上是一个非负整数计数器,支持两种原子操作:
- P操作(Proberen,测试):请求资源,计数器减1。若计数器为0则阻塞线程。
- V操作(Verhogen,增加):释放资源,计数器加1并唤醒等待线程。
数学表示为:
信号量类型[编辑 | 编辑源代码]
二进制信号量[编辑 | 编辑源代码]
计数器取值仅为0或1,相当于互斥锁(Mutex)。
计数信号量[编辑 | 编辑源代码]
计数器可取任意非负整数值,用于控制多实例资源访问。
POSIX信号量实现[编辑 | 编辑源代码]
C语言通过`<semaphore.h>`提供信号量支持:
#include <semaphore.h>
// 初始化无名信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 销毁信号量
int sem_destroy(sem_t *sem);
// P操作(等待)
int sem_wait(sem_t *sem); // 阻塞版本
int sem_trywait(sem_t *sem); // 非阻塞版本
// V操作(释放)
int sem_post(sem_t *sem);
代码示例[编辑 | 编辑源代码]
生产者-消费者问题[编辑 | 编辑源代码]
经典案例展示信号量如何解决线程同步问题:
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#define BUFFER_SIZE 5
sem_t empty, full, mutex;
int buffer[BUFFER_SIZE];
int in = 0, out = 0;
void *producer(void *arg) {
for (int i = 0; i < 10; i++) {
sem_wait(&empty); // 等待空位
sem_wait(&mutex); // 进入临界区
buffer[in] = i;
printf("Produced: %d\n", buffer[in]);
in = (in + 1) % BUFFER_SIZE;
sem_post(&mutex); // 离开临界区
sem_post(&full); // 增加可用产品计数
}
return NULL;
}
void *consumer(void *arg) {
for (int i = 0; i < 10; i++) {
sem_wait(&full); // 等待产品
sem_wait(&mutex); // 进入临界区
printf("Consumed: %d\n", buffer[out]);
out = (out + 1) % BUFFER_SIZE;
sem_post(&mutex); // 离开临界区
sem_post(&empty); // 增加空位计数
}
return NULL;
}
int main() {
pthread_t prod, cons;
sem_init(&empty, 0, BUFFER_SIZE);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);
pthread_create(&prod, NULL, producer, NULL);
pthread_create(&cons, NULL, consumer, NULL);
pthread_join(prod, NULL);
pthread_join(cons, NULL);
sem_destroy(&empty);
sem_destroy(&full);
sem_destroy(&mutex);
return 0;
}
输出示例:
Produced: 0 Consumed: 0 Produced: 1 Consumed: 1 ... Produced: 9 Consumed: 9
工作原理图解[编辑 | 编辑源代码]
实际应用场景[编辑 | 编辑源代码]
1. 数据库连接池:通过计数信号量控制最大连接数 2. 打印任务队列:二进制信号量管理打印机访问 3. 多线程下载:信号量限制同时下载的线程数量 4. GUI事件处理:防止界面线程与工作线程冲突
常见问题[编辑 | 编辑源代码]
死锁风险[编辑 | 编辑源代码]
错误的使用顺序可能导致死锁:
// 错误示例:反向锁定顺序
thread1: sem_wait(A); sem_wait(B);
thread2: sem_wait(B); sem_wait(A);
优先级反转[编辑 | 编辑源代码]
高优先级线程因等待低优先级线程持有的信号量而被阻塞。解决方案包括:
- 优先级继承协议
- 优先级天花板协议
扩展阅读[编辑 | 编辑源代码]
- 信号量与互斥锁的区别:
特性 | 信号量 | 互斥锁 |
---|---|---|
所有者 | 无 | 有(锁定线程) |
计数 | 可大于1 | 只能是0/1 |
用途 | 同步 | 互斥 |
- 其他同步机制:
- 条件变量(Condition Variables)
- 读写锁(Read-Write Locks)
- 屏障(Barriers)
最佳实践[编辑 | 编辑源代码]
1. 始终初始化信号量计数器为正确值 2. 避免信号量的嵌套使用 3. 使用命名信号量跨进程同步时注意清理 4. 考虑使用RAII模式管理信号量资源 5. 在实时系统中优先考虑优先级继承信号量