系列文章目录
第二章:进程的描述与控制
文章目录
- 系列文章目录
- 前言
- 一、信号量的定义和机制:
信号量类型
1.整型信号量:
2.记录型信号量:
- 二、例子 生产者-消费者问题:
- 总结
前言
上文我们讲了硬件同步机制来解决诸进程互斥进入临界区的问题,这一篇我们来讲讲信号量机制来进行进程的同步,包括简单的信号量的应用等等,为什么我们要学习它呢,其实我感觉是因为它通过阻塞等待(而非忙等)的方式管理资源访问,适用于更复杂的同步场景。下面我们来开始进行学习。
一、信号量定义和机制:
这里面的内容可能会有点罗里吧嗦的,我尽量根据我的理解来给大家尽量清晰易懂的讲一遍。
- 信号量是一个整型变量(
sem
),配合两个原子操作(P
/V
)实现同步:- P操作(Proberen,尝试获取):检查信号量是否允许进入临界区,若不允许则阻塞。
- V操作(Verhogen,释放):释放资源,唤醒等待的线程。
- 操作系统支持:信号量的
P
/V
操作由内核调度器管理,通过系统调用实现阻塞和唤醒。
信号量类型
类型 | 描述 | 用途 |
---|---|---|
二进制信号量 | 值域为 0 或 1 ,类似互斥锁。 | 实现互斥访问(单资源场景) |
计数信号量 | 值域为非负整数,表示可用资源数量。 | 管理多个同类资源(如线程池) |
1.整型信号量:
这里面的整形信号量定义为一个用于表示资源数目的整型量S.其实就相当于资源数目。
wait(s){while(s<=0);//一直在这里等待 s--;
}
signal(S);{S++;
}
这里我们需要注意到的细节有:Wait和Signal是两个原子操作,它们在执行时是不可中断的。当一个进程在修改某信号量时,没有其他进程可同时对该信号量进行修改。此外,在Wait操作中,对S值的测试和做S=S-1操作时都不可中断。(这里面我们后面会用到就是缓冲区也属于共享资源)
2.记录型信号量:
其实我在学完这个进程的同步以后,我感觉重中之重就是这个记录型信号量,包括我们后面要分析的生产者-消费者,哲学家就餐等等经典进程问题都与这个脱不了关系,所以这个我尽量给大家讲的详细一点。
首先我们要知道为什么会出现记录型信号量呢,肯定是原来的整型信号量不够使了,因为在原来的操作里,只要信号量S<=0,就会不断的进行测试。因此,该机制并未遵循“让权等待”的准则(其实就是没有释放CPU的资源,占用着),使得进程处于忙等的状态
而记录型信号量机制则是一种不存在“忙等”现象的进程同步机制。
我们还尤其要注意一点的使在采取了“让权等待”的策略后,又会出现多个进程等待访问同一临界资源的情况。所以在信号量机制中,除了需要一个用于代表资源数目的整型变量value外,还应增加一个进程链表指针list,用于链接上述的所有等待进程。下面我们详细的分析
typedef struct{int value;struct process_control_block*list;//等待的进程队列}semaphore;wait(semaphore *S){S->value--
if(S->value < 0)block(s->list);}Signal(semaphore *S){S->value++;if(S->value<=0)wakeup(S->list);
}
wait操作:
这里面的S->value的初值表达的就是系统中某类资源的数目,每次wait就是占用一个资源,是系统中 可供分配的该类资源数减少一个,所以S->value--;当value的值小于0时,肯定资源不够用了要把当前进程阻塞掉,放弃处理机,然后插入到链表的最后。所以此时当S->value的值小于0的绝对值就代表链表中已经阻塞进程的数目。
signal操作:
signal操作其实就是当前进程已经执行完毕,此时把执行进程释放一个单位的资源,使系统中可供分配的该类资源数增加一个,即value++,若加1后仍<=0,则表示该信号量链表中仍有等待该资源的进程被阻塞,此时使用wakeup原语,将链表当中的第一个等待进程唤醒
还要记得如果value的初值为1,表示只允许一个进程访问临界资源,此时的信号量转化为互斥信号量,用于进程的互斥。
所以我想给大家再来总结一遍:
就是啥呢 S->value = 0代表正好,资源正好被够用。
S-> value的值就是资源的数目
S->value<0的绝对值就是等待中的进程的个数。
而在这里我也提前给大家总结一下,就是我们在学到后面如果想要实现互斥访问的话肯定需要加锁,至于值呢我在学习的时候也有点容易混,我们知道互斥就是只允许一个进程访问临界资源,所以我们的值肯定要设为1,然后如果想让有几个可以访问就把值设为几这都是可以的,具体问题具体分析吧.
二、例子生产者-消费者问题:(用记录型信号量)
Semaphore empty = N; // 缓冲区空槽数
Semaphore full = 0; // 缓冲区已填充数
Semaphore mutex = 1; // 互斥访问缓冲区void producer() {while (1) {P(&empty); // 等待空槽P(&mutex);// 生产数据并放入缓冲区V(&mutex);V(&full); // 增加已填充数}
}void consumer() {while (1) {P(&full); // 等待数据P(&mutex);// 从缓冲区取数据并消费V(&mutex);V(&empty); // 增加空槽数}
}
这是最简单的经典同步问题,同步与互斥相结合,这里面要用到多个信号量,这里面加锁我们其实也可以记着,就是我们在修改资源的时候,需要加锁,进行互斥操作,而生产者和消费者又要满足同步,因为只有生产者生产出来一个馒头,消费者才能拿这个馒头吃,所以对这些个资源信号量需要成对的出现,但他们分别处于不同的程序中。
总结
以上就是今天要讲的内容,今天就给大家简单的讲一下整形信号量和记录型信号量和生产者消费者问题,接下来我会给大家讲AND型信号量和信号量集和经典的同步问题,谢谢大家,我会持续更新的。