2.3 進程同步
2.3.1 進程同步的基本概念
臨界資源
一次僅允許一個進程使用的資源稱爲臨界資源,如打印機等物理設備。對臨界資源的訪問必須互斥地進行,在每個進程中,訪問臨界資源的那段代碼稱爲臨界區。
對臨界資源的訪問可分爲4個過程:
do{
entry section;//進入區
critical section;//臨界區
exit section;//退出區
remainder section;//剩餘區
}while(true)
同步(直接制約)
指爲完成某種任務而建立的兩個或多個進程,這些進程因爲需要在某些位置上協調他們的工作次序而等待、傳遞信息所產生的制約關係。
互斥(間接制約)
當一個進程進入臨界區使用臨界資源時,另一個進程必須等待,當佔用臨界資源的進程退出臨界區時,另一種進程才允許訪問此臨界資源。
同步機制應遵循的準則:空閒讓進、忙則等待、有限等待、讓權等待。
2.3.2 信號量
信號量機制是用來解決互斥與同步問題的機制,它只能被兩個標準的原語wait(S)
和signal(S)
訪問,也稱爲“P操作”和“V操作”。
原語是指完成某種功能且不被分割、不被中斷執行的操作序列,通常可由硬件實現。
整型信號量
wait(S){
while(S<=0)
S--;
}
signal(S){
S++;
}
記錄型信號量
typedef struct{
int value;//資源數目
struct process *L;//進程鏈表
}semaphore;
//申請資源
void wait(semaphore S){
S.value--;
if(S.value<0){//資源分配完
add this process to S.L;
block(S.L);//自我阻塞
}
}
//釋放資源
void signal(semaphore S){
S.value++;
if(S.value<=0){//仍有阻塞的進程
remove a process P from S.L;
wakeup(P);//喚醒S.L中第一個進程
}
}
利用信號量實現同步
當x執行完,y纔可以執行。
semaphore S=0;//初始化信號量
P1(){
x; //x語句
V(S); //告訴進程P2,x已經完成
}
P2(){
P(S); //檢查語句x是否運行完成
y; //檢查無誤,運行y語句
}
若P2先執行到P(S)時,S爲0,執行P操作會把進程P2阻塞,並放入阻塞隊列;當進程P1中的x執行完後,執行V操作,把P2從阻塞隊列中放回就緒隊列,當P2得到處理機時,得以繼續運行。
利用信號量執行進程互斥
semaphore S=1;//初始化信號量,可以資源數爲1
P1(){
P(S); //準備開始訪問臨界資源,加鎖
進程P1的臨界區;
V(S); //訪問結束,解鎖
}
P2(){
P(S); //準備開始訪問臨界資源,加鎖
進程P2的臨界區;
V(S); //訪問結束,解鎖
}
當沒有進程在臨界區時,任意一個進程要進入臨界區,就要執行P操作:把S的值減爲零,然後進入臨界區;當有進程存在於臨界區時,S的值爲零,再有進程要進入臨界區執行操作時將會被阻塞,直至在臨界區中的進程退出,這樣便實現了臨界區的互斥。
2.3.3 管程
管程實質上是一個抽象類,這個抽象類有好幾個成員變量,系統中任何設備都可通過這幾個成員變量進行區分和描述;管程中,還有對這些成員變量進行操作的一組成員函數,例如,在對外設的操作中會有read,write這類函數,假如進程P0要使用一臺打印機,於是管程這個抽象類就會利用初始值語句對自身的幾個成員變量賦初值,特定的幾個初值可讓管城表示成一臺打印機,進程P0進入管程後,通過調用過程中的成員函數對這臺打印機進行操作,每次進入這個管程的只能是一個進程。
2.3.4 經典同步問題
生產者-消費者問題
問題描述:一組生產者進程和一組消費者進程共享一個初始爲空,大小爲n的緩衝區。只有緩衝區沒滿時,生產者才能把消息放入緩衝區,否則必須等待;只有緩衝區不爲空時,消費者才能從中取出消息,否則必須等待,由於緩衝區是臨界資源,他只允許一個生產者放入消息,和一個消費者從中取出消息。
關係分析:生產者和消費者對緩衝區的訪問是互斥關係。同時,生產者和消費者又是同步關係。
進程描述:
semaphore mutex=1; //臨界區互斥信號量
semaphore empty=n; //空閒緩衝區
semaphore full=0; //已使用緩衝
prodecer(){//生產者進程
while(1){
produce an item in nextp;//生產數據
P(empty); //獲取空緩存區單元
P(mutex); //互斥夾緊
add nextp to buffer; //將數據放入緩存區
V(mutex); //離開臨界區,釋放互斥信號
V(full); //已使用緩衝區數加一
}
}
prodecer(){//消費者進程
while(1){
P(full); //獲取已使用緩存區單元
P(mutex); //互斥夾緊
remove an item from buffer;//從緩衝區取出數據
V(mutex); //離開臨界區,釋放互斥信號
V(full); //空緩衝區數加一
consume the item; //消費數據
}
}
生產者-消費者問題進階
問題描述:桌子上有一個盤子,每次只能向其中放入一個水果,爸爸專門向盤子中放蘋果,媽媽專門向盤子中放橘子,兒子穿等吃盤子中的橘子,女兒專等吃盤子中的蘋果。只有盤子爲空時,爸爸或媽媽纔可像盤子中放入一個水果,僅當盤子中有自己需要的水果時,兒子或女兒纔可從盤子中取出。
關係分析:爸爸和媽媽是互斥關係,爸爸和女兒是同步關係,媽媽和兒子是同步關係,女兒和兒子沒有同步和互斥關係。
進程描述:
semaphore plate=1, apple=0, orange=0;
dad(){
while(1){
prepare an apple;
P(plate); //互斥向盤中取放水果
put the apple on the plate;
V(apple); //允許取蘋果
}
}
mom(){
while(1){
prepare an orange;
P(plate); //互斥向盤中取放水果
put the orangeon the plate;
V(orange); //允許取橘子
}
}
son(){
while(1){
P(orange); //互斥取橘子
put the orange on the plate;
V(plate); //允許向盤中取放水果
eat the orange;
}
}
mom(){
while(1){
P(apple); //互斥取蘋果
put the apple on the plate;
V(plate); //允許向盤中取放水果
eat the apple;
}
}
讀者-寫者問題
問題描述:有讀者和寫者兩組併發進程共享一個文件。要求:①允許多個讀者可以同時對文件執行讀操作②只允許一個寫者往文件中寫信息③任意寫者在完成寫操作之前,不允許其他讀者和寫者工作④寫者執行寫操作前,應讓有的讀者和寫者全部退出。
關係分析:讀者和寫者互斥,寫者和寫者互斥,讀者和讀者不互斥。
進程描述:
int count=0; //用於記錄當前讀者數量
semaphore mutex=1; //用於保證更新count變量時的互斥
semaphore mutex=1; //用於保證讀者和寫着互斥訪問文件
writer(){ //寫者進程
while(1){
P(rw); //互斥訪問共享文件
writing;
V(rw); //釋放共享文件
}
}
reader(){ //讀者進程
while(1){
P(mutex); //互斥訪問count變量
P(rw); //阻止寫進程寫
count++;
V(mutex);
reading;
P(mutex); //互斥訪問count變量
count--;
V(rw); //允許寫進程寫
V(mutex);
}
}
哲學家進餐問題
問題描述:一張圓桌邊上,坐着五名哲學家。每兩名哲學家之間的桌上擺一根筷子,兩根筷子中間是一碗米飯。哲學家在思考時並不影響他人。只有當哲學家飢餓時才試圖拿起左右兩根筷子,若筷子已在他人手上,則需要等待。飢餓的哲學家。只有同時拿到了兩根筷子纔可以開始進餐,進餐完畢後放下筷子繼續思考。
關係分析:相鄰哲學家對中間筷子的訪問是扶持關係。
進程描述:
semaphore chopsticks[5]={1,1,1,1,1};
semaphore mutex=1; //取筷子信號量
P(int i){ //i號哲學家的進程
do{
P(mutex); //在取筷子前獲得互斥量
P(chopsticks[i]);//取左邊筷子
P(chopsticks[(i+1)%5]);//取右邊筷子
V(mutex); //釋放取筷子信號量
V(chopsticks[i]);//放回左邊筷子
V(chopsticks[(i+1)%5]);//放回右邊筷子
think;
}while(1);
}
吸菸者問題
問題描述:假設一個系統有三個抽菸者進程和一個供應者進程,每個抽菸者不停的捲菸並抽菸,但要抽掉一支菸需要三種材料:菸草、紙、膠水。三個抽菸者中第一個有菸草,第二個有紙,第三個有膠水。供應者進程無限地提供三種材料,但每次僅將兩種材料放到桌子上。剩下擁有那種材料的抽菸者卷一根菸抽調它,並給供應者一個信號,告訴已完成,此時供應者會將另外兩種材料放到桌上,如此重複,讓三個抽菸者輪流地抽菸。
關係分析:供應者與三個抽菸者分別是同步關係,三個抽菸者對抽菸這個動作互斥。
進程描述:
semaphore offer1=0;//菸草和紙組合的資源
semaphore offer2=0;//菸草和膠水組合的資源
semaphore offer3=0;//膠水和紙組合的資源
semaphore finish=0;//抽菸是否完成
Process P1(){ //供應者
while(1){
int random;
random=random%3;
if(random==0)
V(offer1);//提供菸草和紙
else if(random==1)
V(offer2);//提供菸草和膠水
else
V(offer1);//提供膠水和紙
P(finish);
}
}
Process P2(){ //擁有菸草者
while(1){
P(offer3);
捲菸,抽掉;
V(finish);
}
}
Process P3(){ //擁有紙者
while(1){
P(offer2);
捲菸,抽掉;
V(finish);
}
}
Process P4(){ //擁有膠水者
while(1){
P(offer1);
捲菸,抽掉;
V(finish);
}
}
附:java實現的生產者-消費者模型
import java.util.LinkedList;
import java.util.Queue;
import java.util.Random;
/**
* 生產者消費者問題:使用Object.wait() / notify()方法實現。
* Object.wait():當緩衝區空/滿時,消費者/生產者線程停止執行。
* Object.notify():當消費者/生產者執行完動作,向其他線程發出可執行的通知。
*/
public class ProducersAndConsumers {
private static final int CAPACITY = 5;//緩衝區容量
public static void main(String args[]){
Queue<Integer> queue = new LinkedList<Integer>();
//創建一個生產者,一個消費者(也可以是多個)
Thread producer = new Producer("Producer", queue, CAPACITY);
Thread consumer = new Consumer("Consumer", queue, CAPACITY);
producer.start();
consumer.start();
}
// 生產者
public static class Producer extends Thread{
private Queue<Integer> queue;
String name;
int maxSize;
int i = 0;
public Producer(String name, Queue<Integer> queue, int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run(){
while(true){
synchronized(queue){
while(queue.size() == maxSize){
try {
System.out .println("緩衝區已滿, 生產者" + name + "需要等待消費者消費。");
queue.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
System.out.println(name + " 生產數據:" + i);
queue.offer(i++);
queue.notifyAll();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
// 消費者
public static class Consumer extends Thread{
private Queue<Integer> queue;
String name;
int maxSize;
public Consumer(String name, Queue<Integer> queue, int maxSize){
super(name);
this.name = name;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run(){
while(true){
synchronized(queue){
while(queue.isEmpty()){
try {
System.out.println("緩衝區空,消費者" + name + "要等待生產者生產。");
queue.wait();
} catch (Exception ex) {
ex.printStackTrace();
}
}
int x = queue.poll();
System.out.println(name + " 把數據 " + x + " 消費了。");
queue.notifyAll();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}