信号量(本文所指主要是多元信号量)
1.什么是信号量?
- 信号量是用在多线程多任务同步的,通过信号量的资源数来决定是否阻塞线程
- 信号量可以在没有资源时阻塞线程,也可以主动去增加某个线程所用到的资源数
- 如果信号量的资源数充足,则可能发生多个线程同时访问共享资源,因此需要配合
mutex
一起使用,进行线程同步
2.主要的特点是什么?
- 可以指定资源数目,比如在生产者消费者模型中的队列中
存在的任务数
与空闲的任务坑位
都是资源数目 - 对比条件变量:条件变量需要程序员自己判断条件是否满足,在满足条件的情况下会阻塞相应的线程;但是信号量不需要
3.应用场景是什么?
- 生产者消费者模型
- 生产者线程对应一个信号量
- 消费者线程对应一个信号量
4.常用的API函数
// 1.包含头文件
#include <semaphore.h>
// 2.定义信号量
sem_t sem;
// 初始化信号量
// sem: 信号量地址
// pshared: 0->线程同步 1->进程同步
// value: 资源数目
int sem_init(sem_t *sem, int pshared, unsigned int value);
// 访问资源
// 函数被调用,sem的资源数减1
// 当资源数 > 0, 线程不会阻塞,线程会占用一个资源,因此资源数-1;
// 当资源数 = 0, 表示资源被耗尽,线程阻塞,等待被唤醒
int sem_wait(sem_t *sem);
// 功能同上,但是如果资源数 = 0, 函数会立即返回错误码,线程不会阻塞
int sem_trywait(sem_t *sem);
// 功能同上,如果资源数 = 0, 线程阻塞,等待abs_timeout对应的时间后,解除阻塞,向下执行
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
// 调用该函数,会给相应的信号量的资源数加1
int sem_post(sem_t *sem);
// 查看信号量的资源数
int sem_getvalue(sem_t *sem, int *sval);
5.实现生产者消费者模型的代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#define QUEUEMAX 10
// mutex与semaphore
pthread_mutex_t mutex;
sem_t cons_sem;
sem_t prod_sem;
// 节点
typedef struct node
{
int num;
struct node* next;
}Node;
// 指向头结点的指针
Node* head = NULL;
int count = 0;
/* 节点插入方式如下所示
* head-->null
* newNode1
* head-->newNode1-->null
* newNode2
* head-->newNode2-->newNode1-->null
* .....
*/
void* producer(void* arg) {
while (1) {
sem_wait(&prod_sem); // 生产资源减1,无生产资源时阻塞线程
Node* newNode = (Node*)malloc(sizeof(Node));
newNode->num = rand() % 100;
pthread_mutex_lock(&mutex);
newNode->next = head;
head = newNode;
count++;
printf("+++Producer id: %ld, number: %d, count: %d\n", pthread_self(), newNode->num, count);
pthread_mutex_unlock(&mutex);
sem_post(&cons_sem); // 消费资源加1,消费者线程可感知
sleep((unsigned int)rand() % 2);
}
return NULL;
}
void* consumer(void* arg) {
while (1) {
// 应该将sem_wait防置在mutex锁之前,这是为了防止可能存在的死锁
/*
假设mutex上锁放在sem_wait之前
pthread_mutex_lock(&mutex);
sem_wait(&cons_sem);
如果某个消费者线程上锁之后,运行至sem_wait函数处,发现资源数变为0,因此当前线程阻塞;
同时因为当前线程获取了锁,导致其他线程均被阻塞;
此时所有的线程均被阻塞,造成死锁现象
所以不能将sem_wait放置在mutex锁之后!!!!
*/
sem_wait(&cons_sem);
pthread_mutex_lock(&mutex);
Node* curNode = head;
count--;
printf("---Consumer id: %ld, number: %d, count: %d\n", pthread_self(), curNode->num, count);
head = head->next;
free(curNode);
pthread_mutex_unlock(&mutex);
sem_post(&prod_sem);
sleep((unsigned int)rand() % 2);
}
return NULL;
}
int main() {
// 初始化锁和条件变量
pthread_mutex_init(&mutex, NULL);
sem_init(&cons_sem, 0, 0);
sem_init(&prod_sem, 0, QUEUEMAX);
// t1是生产者线程数组 t2是消费者线程数组
pthread_t t1[5], t2[5];
// 创建线程
for (int i = 0; i < 5; i++) {
pthread_create(&t1[i], NULL, producer, NULL);
}
for (int i = 0; i < 5; i++) {
pthread_create(&t2[i], NULL, consumer, NULL);
}
// 回收线程
for (int i = 0; i < 5; i++) {
pthread_join(t1[i], NULL);
}
for (int i = 0; i < 5; i++) {
pthread_join(t2[i], NULL);
}
// 销毁互斥锁
pthread_mutex_destroy(&mutex);
// 销毁semaphore
sem_destroy(&cons_sem);
sem_destroy(&prod_sem);
return 0;
}
参考文献:
①《程序员的自我修养》
② https://subingwen.cn/linux/thread-sync/