信号量(Semaphore)是一种用于多线程或多进程同步的机制,由荷兰计算机科学家 Edsger Dijkstra 提出。信号量的核心思想是通过一个整数值来控制对共享资源的访问,避免竞争条件(Race Condition)。信号量的操作主要包括 P 操作(等待)和 V 操作(释放),分别对应 sem_wait()
和 sem_post()
。
1. 信号量的基本概念
- 信号量值:
- 信号量是一个非负整数,表示可用资源的数量。
- 当信号量值为 0 时,表示没有可用资源,线程或进程需要等待。
- P 操作(Proberen,尝试):
- 将信号量值减 1。
- 如果信号量值小于 0,则线程或进程阻塞,直到信号量值大于 0。
- V 操作(Verhogen,增加):
- 将信号量值加 1。
- 如果有线程或进程在等待信号量,则唤醒其中一个。
2. 信号量的类型
- 二进制信号量:
- 信号量的值只能是 0 或 1。
- 类似于互斥锁(Mutex),用于实现临界区的互斥访问。
- 计数信号量:
- 信号量的值可以是任意非负整数。
- 用于控制多个资源的访问。
3. 信号量的操作
(1)初始化信号量
- 使用
sem_init()
初始化信号量。int sem_init(sem_t *sem, int pshared, unsigned int value);
sem
:信号量对象。pshared
:是否在进程间共享(0 表示线程间,非 0 表示进程间)。value
:信号量的初始值。
(2)P 操作(sem_wait
)
- 将信号量值减 1,如果信号量值为 0,则阻塞。
int sem_wait(sem_t *sem);
(3)V 操作(sem_post
)
- 将信号量值加 1,并唤醒等待的线程或进程。
int sem_post(sem_t *sem);
(4)销毁信号量
- 使用
sem_destroy()
销毁信号量。int sem_destroy(sem_t *sem);
4. 信号量的使用示例
(1)二进制信号量(实现互斥锁)
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>sem_t sem; // 信号量
int shared_data = 0;void* thread_func(void* arg) {sem_wait(&sem); // P 操作shared_data += 10;printf("Thread updated shared_data: %d\n", shared_data);sem_post(&sem); // V 操作return NULL;
}int main() {pthread_t tid1, tid2;sem_init(&sem, 0, 1); // 初始化信号量,初始值为 1pthread_create(&tid1, NULL, thread_func, NULL);pthread_create(&tid2, NULL, thread_func, NULL);pthread_join(tid1, NULL);pthread_join(tid2, NULL);sem_destroy(&sem); // 销毁信号量return 0;
}
(2)计数信号量(控制资源访问)
#include <semaphore.h>
#include <pthread.h>
#include <stdio.h>#define MAX_RESOURCES 3sem_t sem; // 信号量void* thread_func(void* arg) {sem_wait(&sem); // P 操作printf("Thread acquired resource\n");sleep(1); // 模拟资源使用printf("Thread released resource\n");sem_post(&sem); // V 操作return NULL;
}int main() {pthread_t tid[5];sem_init(&sem, 0, MAX_RESOURCES); // 初始化信号量,初始值为 3for (int i = 0; i < 5; i++) {pthread_create(&tid[i], NULL, thread_func, NULL);}for (int i = 0; i < 5; i++) {pthread_join(tid[i], NULL);}sem_destroy(&sem); // 销毁信号量return 0;
}
5. 信号量的应用场景
- 互斥访问:
- 使用二进制信号量实现临界区的互斥访问。
- 资源池管理:
- 使用计数信号量管理有限资源(如数据库连接池、线程池)。
- 生产者-消费者问题:
- 使用信号量同步生产者和消费者线程。
- 读者-写者问题:
- 使用信号量控制读者和写者的访问顺序。
6. 信号量的注意事项
- 死锁:
- 如果多个线程或进程互相等待对方释放信号量,可能导致死锁。
- 优先级反转:
- 低优先级线程持有信号量时,高优先级线程可能被阻塞。
- 信号量泄漏:
- 确保每次
sem_wait()
后都有对应的sem_post()
,否则可能导致信号量泄漏。
- 确保每次
总结
- 信号量:是一种同步机制,用于控制对共享资源的访问。
- P 操作:等待信号量,减少信号量值。
- V 操作:释放信号量,增加信号量值。
- 应用场景:互斥访问、资源池管理、生产者-消费者问题等。
信号量是多线程和多进程编程中非常重要的工具,合理使用可以有效避免竞争条件和实现复杂的同步逻辑。