进程概念的引入
从静态角度看,进程 由PCB、程序段和数据空间三部分组成,用户可通过函数调用建立和撤消进程,通常用户进程被建立后,随着作业运行正常或不正常结束而撤消。
PCB是进程存在的唯一标志,存放进程的特征和变化过程及控制进程所需的所有数据, 是进程控制和调度的依据和操作对象。
PCB中的内容:进程标识符、处理器状态 、调度信息、控制信息
项目 | 进程 | 程序 |
---|---|---|
动态性 | 动态的(进程是程序的执行) | 静态的(程序是有序代码的集合) |
并发性 | √ | ×(因为没有PCB) |
. | 独立、异步 | 静态的文件 |
具有挂起操作的进程状态转换图
P、V操作
- 临界资源:一次仅允许一个进程访问的资源,
- 临界区: 并发进程中访问临界资源的程序段。
有m个进程共享同一临界资源,若使用信号量机制实现对临界资源的互斥访问,则信号量值的变化范围是1 ~ -(m-1)。
- 两个原子操作:
P操作 wait(S):请求一个单位的资源;
若减1后S.value < 0,则表示资源已分配完毕。故进程调用block原语进行自我阻塞,
并被插入到等待队列尾。
V操作 signal(S):释放一个单位的资源;
若加1后S.value <= 0,则表示等待队列中仍有进程等待该资源。故进程调用wakeup原语唤醒位于队首的那个进程。
生产者 - 消费者问题:
一组生产者进程生产产品给一组消费者消费。为使他们并发执行,设有一个n个缓冲区的缓冲池,生产者一次向一个缓冲区投入消息,消费者从一个缓冲区中取得消息。
int in=0, out=0; //放入和取出的数据地址
item buffer[n]; //缓冲池
semaphore mutex = 1, //
empty = n, //缓冲池中 空缓冲区的数量
full = 0; // 满缓冲区的数量
void proceducer(){
do{
...
//生产者生产一个产品nextp;
...
wait(empty); //传说中的P操作(empty-1)
wait(mutex);//------------请求锁------------
buffer[in] = nextp;
in = (in+1) % n;
signal(mutex);//----------释放锁-------------
signal(full); //V操作 -> (full+1)
}while(TRUE);
}
void consumer(){
do{
wait(full); //P操作 -> (full-1)
wait(mutex);//------------请求锁-----------
nextc = buffer[out];
out = (out+1) % n;
signal(mutex);//----------释放锁------------
signal(empty); //V操作 -> 又多了一个空缓存区
...
//消费者开始消费取出的产品nextc;
...
}while(TRUE);
}
void main(){
cobegin
proceducer();
consumer();
coend
}
哲学家进餐问题
哲学家们的生活方式是交替进行思考和进餐。现有五个哲学家们共用一个圆桌,有五张椅子,五个碗,五只筷子,但是一个哲学家想吃饭时,需同时拿起最近的左右筷子吃。那么该怎样协调他们吃饭的时间。
1.利用记录型信号量解决
semaphore chopstick chopstick[5] = {1,1,1,1,1};
do{
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
...
//eat
...
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
...
//think
...
}while(TRUE);
但是,当五个哲学家同时饥饿,那么五个人同时拿起左边筷子,那么五个人就无限等待下去了。。。这就死锁了。
解决办法:
(1)至多四个人去拿左边筷子,即可保证至少有一个人可以用餐。
(2)仅当左右两只筷子均可用时,才能进餐。 (AND型信号量)√√
(3)规定奇数号的先拿起左边的,再拿右边的。偶数号则相反。√
2.利用AND型信号量机制解决
semaphore chopstick chopstick[5] = {1,1,1,1,1};
do{
...
//think
...
Swait(chopstick[i], chopstick[(i+1)%5]);
...
//eat
...
Ssignal(chopstick[i], chopstick[(i+1)%5]);
}while(TRUE);
读者 - 写者问题
一个数据文件或记录可被多个进程共享,其中,有些进程要求读("Reader进程"),有些进程则要求写或修改("Writer进程")。
读者优先:一旦读者正在读数据,允许多个读者同时进入读数据,只有当全部读者退出,才允许写者进入写数据
int readcount = 0;
semaphore Fmutex=1; //为保证读者与写者、写者与写者之间的互斥访问
semaphore Rmutex=1, Wmutex=1; //Rmutex为实现对readcount的互斥访问
semaphore Entermutex = 1; //
void reader(){
do{
wait(Rmutex);
if(readcount==0) wait(Fmutex); //readcount==0时,说明读者已经访问完毕,需要释放等待队列中的一个写者进程
readcount++;
signal(Rmutex);
...
//执行 **读** 操作
...
wait(Rmutex);
readcount--; //读取完毕,释放资源
if(readcount==0) signal(Fmutex); //释放Wmutex锁
signal(Rmutex);
}while(TRUE);
}
void writer(){
do{
wait(Entermutex); //保证每次进入Fmutex中的写者进程只有一个 wait(Fmutex);
...
//执行 **写** 操作
...
signal(Fmutex);
signal(Entermutex);
}while(TRUE);
}
void main(){
cobegin
reader();
writer();
coend
}
写者优先:一旦一个写者到来,它应该尽快对文件进行写操作,如果有一个写者在等待,则新到来的读者不允许进行读操作。
这种方案虽然解决写者饥饿问题,但降低了并发程度使系统性能较差。
int readcount = 0;
int writecount = 0;
semaphore Fmutex=1; //为保证读者与写者、写者与写者之间的互斥访问
semaphore Rmutex=1, Wmutex=1; //Rmutex为实现对readcount的互斥访问
semaphore Entermutex = 1;
semaphore Quemutex = 1; //当Writer进程到来时,Reader进程的阻塞队列
void reader(){
do{
wait(Entermutex); //保证每次进入Quemutex中的读者进程只有一个
wait(Quemutex);
wait(Rcount);
if(readcount == 0) wait(Fmutex);
readcount++;
signal(Rcount);
signal(Quemutex);
signal(Entermutex);
...
//执行 **读** 操作
...
wait(Rcount)
if(readecount==0)
signal(Fmutex);
signal(Rcount)
}while(TRUE);
}
void writer(){
do{
wait(Wmutex);
if(writecount == 0) wait(Quemutex);
writecount++;
signal(Wmutex);
wait(Fmutex);
...
//执行 **写** 操作
...
signal(Fmutex);
wait(Wcount);
readcount--;
if(readcount==0) signal(Quemutex);
signal(Wcount);
}while(TRUE);
}
void main(){
cobegin
reader();
writer();
coend
}