临界区:并发进程中与互斥共享变量相关的程序段
临界区管理
同时进入临界区
process P1
begin
while inside2 do [ ];
inside1 := true;
临界区;
inside1 := false;
end;
process P2
begin
while inside1 do [ ];
inside2 := true;
临界区;
inside2 := false;
end;
同时等待进入临界区
process P1
begin
inside1 := true;
while inside2 do [ ];
临界区;
inside1 := false;
end;
process P2
begin
inside2 := true;
while inside1 do [ ];
临界区;
inside2 := false;
end;
c
// 测试锁
TS(x) {
if (x==false) {x=true;return true;
} else return false;
}
Boolean lock;
lock=false; // 临界区可用
process Pi {
Boolean pi;
repeat pi=TS(lock) until pi; // 循环请求锁
临界区
lock=false; // 解锁
}
// 交换锁
swap(a, b){temp=a;a=b;b=temp;}
Boolean lock;
lock=false; // 临界区可用
process Pi {
Boolean pi;
pi=true;
repeat swap(lock, pi) until !pi; // 循环建立锁
临界区;
lock=false; // 解锁
}
记录型信号量:一种带数值的软资源
typedef struct semaphore {
int value; // 信号量值
struct pcb *list; // 信号量等待进程队列指针
}
c
procedure P(semaphore:s) {
s -= 1; // 信号量-1
if (s<0) W(s); // 若信号量小于0,则调用进程被置成等待信号量s的状态
}
procedure V(semaphore:s) {
s += 1; // 信号量+1
if (s<=0) R(s); // 若信号量小于等于0,则释放一个等待信号量s的进程
}
c
semaphore s;
s=1;
begin
process Pi {
......
P(s);
临界区;
V(s);
......
}
end;
PV操作解决进程同步问题
1生产者1消费者1缓冲区问题
缓冲区有产品时,消费者可取出产品,否则等待
Int B;
process producer
begin
L1:
produce a product;
B = product;
goto L1;
end;
process consumer
begin
L2:
product = B;
consume a product;
goto L2;
end;
// 正确执行顺序:P→(同步关系1:等待产品)C→(同步关系2:等待缓冲)P→C→P→C...
// 信号量仅仅解决信号传递数据传送需要共享缓冲区
解决思路:
// PV解决1生产者1消费者1缓冲区问题
Int B; // 共享缓冲区
Semaphore sput; // 可以使用的空缓冲区
Semaphore sget; // 缓冲区内可以使用的产品数
sput = 1; // 缓冲区内允许放入一件产品
sget = 0; // 缓冲区内没有产品
process producer {
L1:
produce a product;
P(sput);
B = product;
V(sget);
goto L1;
}
process consumer {
L2:
P(sget);
product = B;
V(sput);
consume a product;
goto L2;
}
// PV解决1生产者1消费者N缓冲区问题
Int B[k]; // 共享缓冲区队列
Semaphore sput; // 可以使用的空缓冲区
Semaphore sget; // 缓冲区内可以使用的产品数
sput = k; // 缓冲区内允许放入k件产品
sget = 0; // 缓冲区内没有产品
Int putptr, getptr; // 循环队列指针
putptr = 0; getptr = 0;
process producer_i {
L1:
produce a product;
P(sput);
B[putptr] = product;
putptr = (putptr+1) mod k;
V(sget);
goto L1;
}
process consumer_j {
L2:
P(sget);
product = B[getptr];
getptr = (getptr+1) mod k;
V(sput);
consume a product;
goto L2;
}
// PV解决N生产者N消费者N缓冲区问题
Int B[k]; // 共享缓冲区队列
Semaphore sput; // 可以使用的空缓冲区
Semaphore sget; // 缓冲区内可以使用的产品数
sput = k; // 缓冲区内允许放入k件产品
sget = 0; // 缓冲区内没有产品
Int putptr, getptr; // 循环队列指针
putptr = 0; getptr = 0;
s1, s2:semaphore; // 互斥使用putptr,getptr
s1 = 1;s2 = 1;
process producer_i {
L1:
produce a product;
P(sput);
P(s1);
B[putptr] = product;
putptr = (putptr+1) mod k;
V(s1);
V(sget);
goto L1;
}
process consumer_j {
L2:
P(sget);
P(s2);
product = B[getptr];
getptr = (getptr+1) mod k;
V(s2);
V(sput);
consume a product;
goto L2;
}
// 苹果橘子问题
Int plate;
Semaphore sp; // 盘子里可以放几个水果
Semaphore sg1; // 盘子里有桔子
Semaphore sg2; // 盘子里有苹果
sp = 1; // 盘子里允许放入一个水果
sg1 = 0; // 盘子里没有桔子
sg2 = 0; // 盘子里没有苹果
process father {
L1: 削一个苹果;
P(sp);
把苹果放入plate;
V(sg2);
goto L1;
}
process mother {
L2: 剥一个桔子;
P(sp);
把桔子放入plate;
V(sg1);
goto L2;
}
process son {
L3: P(sg1);
从plate中取桔子;
V(sp);
吃桔子;
goto L3;
}
process daughter {
L4: P(sg2);
从plate中取苹果;
V(sp);
吃苹果;
goto L4;
}
霍尔管程基于PV操作原语实现
// 互斥调用霍尔管程的信号量
TYPE interf=RECORD
mutex: semaphore; // 调用管程过程前使用的互斥信号量
next: semaphore; // 发出signal的进程挂起自己的信号量
next_count: integer; // 在next上等待的进程数
END;
// 互斥调用霍尔管程的框架
P(IM.mutex);
<过程体>
if IM.next_cout > 0 then V(IM.next);
else V(Im.mutex);
// 霍尔管程的条件变量
x_sem: semaphore; // 与资源相关的信号量
x_count: integer; // 在x_sem上等待的进程数
// 霍尔管程的wait进程
procedure wait(var x_sem: semaphore, var x_count: integer, var IM: interf);
begin
x_count += 1;
if IM.next_count > 0 then V(IM.next);
else V(IM.mutex);
P(x_sem);
x_count -= 1;
end;
// 霍尔管程的signal过程
procedure signal(var x_sem: semaphore, var x_count: integer, var IM: interf);
begin
if x_count > 0 then begin
IM.next_count += 1;
V(x_sem);
P(IM.next); // 进入等待调用管程的队列
IM.next_count -= 1;
end;
end;
// 哲学家问题
TYPE dining_philosophers = MONITOR
var state: array[0..4] of (thinking, hungry, eating);
s: array[0..4] of semaphore;
s_count: array[0..4] of integer;
define pickup, putdown;
use wait, signal;
procedure test(k: 0..4) P
if state[(k-1) mod 5] <> eating and state[k]=hungry
and state[(k+1) mod 5] <> eating then
{state[k] := eating; signal(s[k],s_count[k],IM);}
}
procedure pickup(i:0..4) {
state[i] := hungry;
test(i);
if state[i] <> eating
then wait(s[i], s_count[i], IM);
}
procedure putdown(i:0..4 {
state{i]:=thinking;
test((i-1) mod 5);
test((i+1) mod 5);
}
begin
for i:=0 to 4 do
state[i] := thinking;
end;
begin
process philosopher_i() {
L:thinking();
P(IM.mutex);
dining_philosophers.pickup(i);
if IM.next_count > 0 then V(IM.next); else V(IM.mutex);
eating();
P(IM.mutex);
dining_phi;osophers.putdown(i);
if IM.next_count > 0 then V(IM.next); else V(IM.mutex);
goto L;
}
end;
// 读者写者问题
TYPE read-writer = MONITOR
var Rc, Wc: integer; R, W:semaphore; rc, wc: integer;
define start_read, end_read, start_writer, end_writer;
use wait, signal, check, release;
begin rc:=0;wc:=0;Rc:=0;Wc:=0;R:=0;W:=0; end;
procedure start_read;
begin
if wc > 0 then wait(R,Rc,IM);
rc += 1;
signal(R, Rc, IM); // 连续释放读者
end;
procedure end_read;
begin
rc += 1;
if rc = 0 then signal(W, Wc,IM);
end;
procedure start_write;
begin
wc += 1;
if rc > 0 or wc > 1 then wait(W, Wc, IM);
end;
procedure end_write;
begin
wc += 1;
if wc > 0 then signal(W,Wc,IM);
else signal(R,Rc,IM);
end;
死锁检测和恢复
检测:
可设置两张表格来记录进程使用资源的情况
进程申请资源时,先查该资源是否为其他进程所占用;若资源空闲,则把该资源分配给申请者且登入占用表;否则,则登入进程等待资源表
死锁检测程序定时检测这两张表,若有进程Pi等待资源rk,且rk被进程Pj占用,则说明Pi和Pj具有“等待占用关系”,记为W(Pi,Pj)
死锁检测程序反复检测这两张表,可以列出所有的“等待占用关系”
如果出现W(Pi,Pj),W(Pj,Pk),…,W(Pm,Pn),W(Pn,Pi)时,显然,系统中存在一组循环等待资源的进程:Pi,Pj,Pk,…,Pm,Pn,也就是说出现了死锁
两张表可以用一个矩阵A表示
进程/ A[bij] A [ b i j ] /进程 | P1 | P2 | … | Pn |
---|---|---|---|---|
P1 | b11 | b12 | … | b1n |
P2 | b21 | b22 | … | b2n |
… | … | … | … | … |
Pn | bn1 | bn2 | … | bnn |
其中b_ij=1表示当Pi等待被Pj占用的资源时,=0表示Pi与Pj不存在等待占用关系时
算法
// Warshall的传递闭包算法检测是否有死锁发生
// 即对矩阵A构造传递闭包A*[bij]
for k:= 1 to n do
for i: 1 to n do
for j:= 1 to n do
bij:= bij∪(bij∩bkj)