多线程编程中经常需要对不同的线程进行协同工作,同时为了保证线程访问共享资源的正确性,需要使用线程间通信。而信号量是实现线程间通信的重要方式之一。
信号量(Semaphore)是由荷兰计算机科学家 Dijkstra 在 1965 年首次提出的一种用于进程或线程间同步的机制。信号量可以用于限制线程的数量,保护临界区操作,控制缓存区数据数量等。
信号量的数据结构包含以下属性:
- 值(Value):信号量的初值,表示可用的资源数目。
- 等待队列(Queue):线程等待获取资源时被等待的位置。
Semaphore 通常通过系统调用实现,C 语言中提供的 sem_t 类型就是用于封装信号量的结构体类型。在使用信号量之前,需要调用 sem_init 函数进行初始化,语法如下:
#include
int sem_init(sem_t* sem, int pshared, unsigned int value);
这个函数初始化信号量 sem,pshared 参数指定信号量的类型(0 表示信号量在进程间共享,1 表示信号量在线程间共享),value 表示信号量的初值。如果函数执行成功,返回值为 0,否则返回值为 -1。
在使用信号量之后,可以调用 sem_wait 和 sem_post 函数来进行等待和释放信号量的操作,语法如下:
#include
int sem_wait(sem_t* sem);
int sem_post(sem_t* sem);
sem_wait 函数会将信号量的值减少 1,如果信号量已经为 0,线程会被阻塞等待,直到信号量的值大于 0。
sem_post 函数会将信号量的值增加 1,并且如果有线程因为 sem_wait 函数被阻塞等待信号量,那么会释放一个等待的线程。
在使用完信号量之后,需要调用 sem_destroy 函数来销毁信号量,语法如下:
#include
int sem_destroy(sem_t* sem);
下面是一个使用信号量的例子,实现了两个线程交替输出 A 和 B,代码如下:
#include
#include
#include
#include
#include
sem_t g_sem_a, g_sem_b;
void* ThreadA(void* arg) {
while (1) {
sem_wait(&g_sem_a); // 等待信号量 A
printf("A\n");
sem_post(&g_sem_b); // 发送信号量 B
}
return NULL;
}
void* ThreadB(void* arg) {
while (1) {
sem_wait(&g_sem_b); // 等待信号量 B
printf("B\n");
sem_post(&g_sem_a); // 发送信号量 A
}
return NULL;
}
void SignalHandler(int signo) {
sem_post(&g_sem_a);
sem_post(&g_sem_b);
exit(0);
}
int main() {
pthread_t tid_a, tid_b;
sem_init(&g_sem_a, 0, 1); // 初始化信号量 A
sem_init(&g_sem_b, 0, 0); // 初始化信号量 B
signal(SIGINT, SignalHandler); // 捕获 Ctrl+C 信号
pthread_create(&tid_a, NULL, ThreadA, NULL); // 创建线程 A
pthread_create(&tid_b, NULL, ThreadB, NULL); // 创建线程 B
pthread_join(tid_a, NULL);
pthread_join(tid_b, NULL);
sem_destroy(&g_sem_a); // 销毁信号量 A
sem_destroy(&g_sem_b); // 销毁信号量 B
return 0;
}
在上面的代码中,ThreadA 和 ThreadB 分别表示两个线程。在主线程中,先通过 sem_init 函数初始化了两个信号量 g_sem_a 和 g_sem_b,然后通过 pthread_create 函数创建了两个线程 tid_a 和 tid_b。在这两个线程中,通过 sem_wait 和 sem_post 函数交替等待和发送两个信号量,最终实现了 A 和 B 的交替输出。
使用信号量是多线程编程中常用的同步和互斥手段之一。信号量可以用于限制线程数量,保护临界区的操作,控制缓存区中数据的数量等,并且可以使用反向信号量来解决死锁问题。在使用信号量时需要注意信号量的初始化和销毁,并使用锁保护临界区,避免死锁的问题。