信号量是一种比互斥锁更强大的同步工具,它可以提供更高级的方法来同步并发进程
定义
信号量S是一个整数变量,除了初始化之外,它只能被两个标准原子操作访问:P & V
原子操作(原语)
即指运行中不能被中断的操作
P -> wait() operation
V -> signal() operation
实现
P(S){
while(S <= 0)
do nothing;
S--;
}
V(S){
S++;
}
Binary Semaphore
顾名思义,二值信号量的值只能是0或1,通常将其初始化为1,用于实现互斥锁的功能
semaphore mutex = 1;
process p
{
P(mutex); // 相当于acquire lock
critical section; // 临界区
V(metex); // 相当于release lock
}
如图所示,两辆车争夺一条匝道
假设此时,蓝车进入匝道, 蓝车执行P(mutex)操作
mutex
初始值为1,执行P(mutex)
操作, 由于mutex > 0
不进入循环,执行mutex--
后mutex
值为0- 此时红车如果想要进入匝道,执行P(mutex)操作,由于此时
mutex
满足循环条件,它将在此忙式等待(Busy Waiting, 指占用CPU执行空循环实现等待) - 等到蓝车驶出匝道,执行V(mutex)操作,
mutex++
后mutex
值重新为1 - 之后红车P操作 循环条件不满足,执行
mutex--
,红车驶入匝道,占用匝道资源。假如之后还有车辆驶来,那么它们进入Busy Waiting,以此往复
Counting Semaphore
一般信号量的取值可以是任意数值,用于控制并发进程对共享资源的访问
有了前边的分析,我们可以轻松理解此图。
图中有多辆车共享两条匝道,即信号量road
初始值为2
简要分析流程(假设按图中顺序)
- 红车P操作后,
road = 1
,驶入上边的匝道 - 蓝车1执行P操作,由于不满足循环条件,
road = 0
,驶入下边的匝道 - 蓝车2执行P操作,满足循环条件,进入
Busy Waiting
状态
随后只要红车
或者蓝车1
执行了V操作,那么road值将自增,相当于驶出匝道,蓝车2
即可进入匝道
如果此时红车和蓝车1都驶出了匝道,只有蓝车2在匝道,此时road即为1
- P、V操作是成对出现的
- 信号量代表可用共享资源总量
信号量实现同步
生产-消费者问题
- 缓冲区:相当于存放产品的数组
- 生产者:负责生产产品,当产品数超过最大值时停止生产
- 消费者:负责消费产品,当产品数小于1时停止消费
分析其中的关系可知,
- 生产者(线程)与消费者(线程)之间是协作关系
- 同类线程之间为竞争关系
其中竞争关系,可以通过互斥锁来实现
如何实现协作关系?
如图所示,我们可以设置两个信号量 empty
和 full
,其中empty
负责控制资源总量(K)
分析图中伪代码流程:
假设有三个生产者线程,两个消费者线程
- consumer-01 线程执行
P(full)
操作,由于不满足循环条件,进入busy waiting状态 - consumer-02 线程同样进入busy waiting状态,对应实际场景,缓冲区还没有商品,消费者无法消费。
- producer-03 线程执行
P(empty)
操作,empty = k-1
,开始生产产品后,执行V(full)
操作,full = 1
。 - 假设随后CPU依次调度producer-01 和 producer-02线程,此时
full = 3
,empty = k-3
。可供两个消费者线程争夺资源,假设K = 3
, 那么此时empty = 0
, 相当于缓存区已放满商品,此时如果新来一个生产者 producer-04线程,它将进入等待。
注: 同类线程之间为竞争关系,相当于图中临界区前后(empty 和 full 之间),需要再加入P(mutex) 和 V(mutex)操作实现互斥
参考代码(C)
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
#define THREAD_NUM 4 // 线程总数
#define K 5 // 资源总数
typedef int item;
item Buffer[K];
sem_t empty, full, mutex;
int cur = 0;
int in = 0, out = 0;
void *producer(void *arg)
{
while (1)
{
sem_wait(&empty); // p(empty)
sem_wait(&mutex); // p(mutex)
printf("%lu, 生产了一件商品, current = %d\n", pthread_self(), ++cur);
Buffer[in] = cur;
in = (in + 1) % K;
sem_post(&mutex); // v(mutex)
sem_post(&full); // v(full)
}
pthread_exit(0);
}
void *consumer(void *arg)
{
while (1)
{
sem_wait(&full);
sem_wait(&mutex);
Buffer[out] = -1;
out = (out + 1) % K;
printf("%lu, 取走了一件商品, current = %d\n", pthread_self(), --cur);
sem_post(&mutex);
sem_post(&empty);
}
pthread_exit(0);
}
int main(int argc, char const *argv[])
{
pthread_t ticketAgent_tid[THREAD_NUM];
// 初始化操作
sem_init(&empty, 0, K);
sem_init(&full, 0, 0);
sem_init(&mutex, 0, 1);
// 创建线程
pthread_create(ticketAgent_tid, NULL, producer, NULL);
pthread_create(ticketAgent_tid + 1, NULL, consumer, NULL);
pthread_create(ticketAgent_tid + 2, NULL, consumer, NULL);
pthread_create(ticketAgent_tid + 3, NULL, producer, NULL);
pthread_join(ticketAgent_tid[0], NULL);
pthread_join(ticketAgent_tid[1], NULL);
pthread_join(ticketAgent_tid[2], NULL);
pthread_join(ticketAgent_tid[3], NULL);
// 销毁信号量
sem_destroy(&mutex);
sem_destroy(&full);
sem_destroy(&empty);
return 0;
}