我们的目标是快乐地学习计算机!O(∩_∩)O~
【信号量】
虽然它叫信号,其实从算法理解上和信号没啥关系。更好的理解是用于进程之间通信“我要运行,你不要运行”的变量。
信号量为0(≤0)表示“别动”,信号量是非0(>0)表示“可以动”
【操作】
信号量只有三个操作:
① 赋予一个初始值 (init)
② 判断是不是0,如果是0,挂起,如果是正数,信号量减1 (wait)
③ 信号量加1 (signal)
【应用】
信号量有两个基本应用:
① 互斥锁定:
使用wait(a)和signal(a),中间的进程就会变成锁定运行,其它进程无法干扰,用于同类型的多个并发线程的隔离
举例:两个同学抢一个厕所
init(sig=1) //厕所是空的,可以用
wait(sig) //判断厕所是否可以用,不能用就等待,能用就用
process #0 //上厕所
signal(sig) //告诉大家,我已经出来了,厕所空了
② 同步演化:
使用两个信号量,wait(a)signal(b)和wait(b)signal(a),显然,两者是互相演化的,有#0则#1可以执行,有#1则#0可以执行
举例:一个生产者为一个消费者提供产品(库存空间有限)
init(sig1=1) //"库存现在是空的,可以生产"
init (sig2=0) //"没有产品可以获取"
#0{
wait(sig1) //判断是否有库存,没有就等待消费者获取,有就生产
process #0
signal(sig2) //告诉消费者你可以获取产品了
}
#1{
wait(sig2) //等待可以获取产品的消息,如果没有就等待,有就获取
process #1
signal(sig1) //告诉消费者我已经取走了,库存已经空了
}
【代码实现】
最简单的wait / signal代码
void wait(){
while (sig<=0){;}
sig--;
}
void signal(){
sig++;
}
【实战】
实际应用中,用上述代码是不行的,因为需要进入核心态,关闭中断,否则互斥的问题依然存在:
引用头文件:
定义一个信号量的变量:
sem_t
注意到我们前面说的,信号量只有三个操作:
①赋予初值:
sem_init()函数,第一个变量为选择的信号量对象,第二个参数0位线程1位进程,第三个参数为初值
②判断是否≤0,如果是就挂起,否则信号量减1:
sem_wait()函数,参数为选择的信号量对象
③信号量加1:
sem_post()函数,参数为选择的信号量对象
下面我们来看一个实例:
有A个生产者给B个消费者供应产品,产品的库存(缓冲池)只有N块,生产者之间互斥竞争放入产品,而消费者互斥竞争取走产品,生产者与消费者之间则同步放入和取走产品。若库存满了,则生产者停工,若库存为空,则消费者等待。每个生产者必须生产15个物品,每个消费者能且只能拿15个物品,如图:
以下代码取A=5,B=5,N=8:
#include
#include
#include
#include
#define N 8
#define PRODUCT_NUM 15
struct good{
int id;
int producer;
};
//The origin code gives an algorithm to solve synchronization
//Now we need to solve asynchronization
struct good buffer[N+1];
int readpos = 1, writepos = 1;
sem_t empty, full;
sem_t p_lock,c_lock;
void *produce(void * id){
int i;
for (i = 0; i < PRODUCT_NUM; i++){
sem_wait(&empty);
sem_wait(&p_lock);
buffer[writepos].id = i + 1;
buffer[writepos].producer = *(int*)id;
printf("producer %d create %d-%d At: %d\n",*(int*)id ,*(int*)id, i+1,writepos);
writepos++;
if (writepos > N)
writepos = 1;
sem_post(&p_lock);
sem_post(&full);
}
}
void *consume(void * id){
int i;
for (i = 0; i < PRODUCT_NUM; i++){
sem_wait(&full);
sem_wait(&c_lock);
printf("consumer %d get %d-%d At: %d\n",*(int*)id , buffer[readpos].producer,buffer[readpos].id,readpos);
buffer[readpos].id = - 1;
buffer[readpos++].producer = - 1;
if (readpos > N)
readpos = 1;
sem_post(&c_lock);
sem_post(&empty);
}
}
int main(){
int res1,res2,res3,res4,res5,res6,res7,res8,res9, i;
int id[6]={0,1,2,3,4,5};
pthread_t p1,p2,p3,p4,p5;
pthread_t c1,c2,c3,c4; //c5 is the main thread
for (i = 0; i < N; i++)
buffer[i].producer = - 1;
buffer[i].id = - 1;
sem_init(&full, 0, 0);
sem_init(&empty, 0, N);
sem_init(&p_lock,0,1);
sem_init(&c_lock,0,1);
res1 = pthread_create(&p1, NULL, produce, &id[1]);
res2 = pthread_create(&p2, NULL, produce, &id[2]);
res3 = pthread_create(&p3, NULL, produce, &id[3]);
res4 = pthread_create(&p4, NULL, produce, &id[4]);
res5 = pthread_create(&p5, NULL, produce, &id[5]);
res6 = pthread_create(&c1, NULL, consume, &id[1]);
res7 = pthread_create(&c2, NULL, consume, &id[2]);
res8 = pthread_create(&c3, NULL, consume, &id[3]);
res9 = pthread_create(&c4, NULL, consume, &id[4]);
if (res1 || res2 || res3 || res4 || res5 || res6 || res7 || res8 || res9 != 0){
perror("failed to create thread");
exit(1);
}
consume(&id[5]);
return 0;
}
运行结果(每次运行结果不一定一样,此为其中某一次):