操作系統面試—進程同步(二)

本博文是對上一篇博文的繼續,不足之處歡迎大家批評指正。


一、首先介紹常見的經典同步問題


1、有限緩衝問題

假定有n個緩衝項,信號量mutex提供對緩衝池訪問的互斥要求,並初始化爲1。信號量empty和full代表空緩衝項和滿緩衝項的個數,信號量empty初始化爲n,full初始化爲0。

生產者的代碼如下:

do{

...

//生產出一項

...

wait(empty);

wait(mutex);

...

//添加到緩衝區

...

signal(mutex);

signal(full);

}while(true);

消費者的代碼如下:

do{

wait(full);

wait(mutex);

...

//從緩衝區移走一項

...

signal(mutex);

signal(empty);

...

//消費這一項

...
}while(true);


2、讀者寫者問題

第一讀者-寫者問題:可能導致寫者飢餓,讀者不會進行等待,除非寫者已獲得使用數據庫的權限。

第二讀者-寫者問題:如果一個寫者等待訪問對象,那麼不會有新讀者開始讀操作。這可能引起讀者飢餓。

下面利用信號量進行解決第一讀者-寫者問題:

設置共享數據結構: 

semaphore* mutex,wrt;//都初始化爲1

int readcount;//初始化爲0

readcount用來保存有多少讀者正在執行讀對象。mutex用於確保在更新變量readcount的互斥。wrt供寫者作爲互斥信號量。它作爲第一個進入臨界區的讀者和最後一個離開臨界區的讀者所使用,其他讀者不進行使用。

寫者進程結構:

do{

wait(wrt);

...//寫操作

signal(wrt);

}while(true);

讀者進程結構:

do{

wait(mutex);

readcount++;

if(readcount==1)

   wait(wrt);

signal(mutex);//釋放mutex,那麼另外一個進程又可以進行修改readcount操作了。

...//讀操作

wait(mutex);//注意每次更新readcount時,必須確保互斥訪問,即不能有兩個進程同時改變readcount變量,否則錯誤

readcount--;

if(readcount==0)

   signal(wrt);

signal(mutex);

}while(true);

讀寫鎖常用的幾種情況:

(1)當可以區分哪些進程只需要讀共享數據而哪些進程需要寫共享數據。

(2)當讀者進程比寫者進程多時。——讀寫鎖簡歷開銷通常比信號量或互斥鎖要大,而這個開銷可以通過多個讀者的併發訪問來彌補。


3、哲學家進餐問題

這個問題是比較經典的同步問題。

一個簡單的辦法是通過對每一個筷子設置信號量,但實際上這種解法可能引起死鎖問題。

常用的解決方法有:

(1)最多隻允許四個哲學家在座位上。

(2)只有兩隻筷子都可用時才允許一個哲學家拿起它們。

(3)非對稱解法。奇數哲學家先拿起左邊筷子,再拿起右邊筷子,而偶數哲學家相反。


二:管程

提出:當信號量使用不正確時,很容易產生各類錯誤。

定義:管程是這樣一種數據結構,結構內的多個子程序形成的多個工作線程互斥訪問共享資源。

管程類型提供了一組程序員定義的、在管程內互斥的操作。管程類型不能直接爲各個進程所使用,在管程內定義的子程序只能訪問位於管程內哪些局部聲明的變量和形式參數。這樣管程結構能夠確保一次只有一個進程能在管程內活動。

管程內可能需要一些條件結構:condition x,y;

x.wait();——調用操作的進程會被掛起,知道另一程序調用x.signal(),x.signal()重新啓動一個懸掛的進程。如果沒有懸掛進程,那麼signal操作將沒有作用,這裏和信號量的signal不一樣,後者會影響信號量的變化。

舉個栗子:

假設操作x.signal()爲一個進程P所調用時,有一個懸掛進程Q與條件變量x相關聯。顯然,如果Q允許執行,那麼P必須等待。否則兩個進程將同時在管程內進行。然而在概念上,兩個進程都可以繼續進行,因此將有兩種可能性存在:

(1)喚醒並等待:P等待直到Q離開管程或者等待另外一個條件

(2)喚醒並繼續:進程Q等待直至P離開管程或者等待另外一個條件。


三、基於信號量的管程實現

對每一個管程,都有一個信號mutex(初始化爲1)。進程在進入管程之前必須執行wait(mutex),離開管程之後必須執行signal(mutex)。因爲信號進程必須等待,直到重新啓動的進程離開或等待,所以引入另外一個信號量next(初始化爲0)以供信號掛起自己,提供整型變量next_count以對掛起在next上的進程數量進行計數。因此每個外部子程序F將會變成:

wait(mutex);

F函數體;

if(next_count>0)

 signal(next);

else

 signal(mutex);//如果掛起的進程不止一個,那麼就不能釋放mutex,不然其他進程會進入管程的。

那麼如何實現條件變量?引入信號量x_sem和x_count,都初始化爲0;

x.wait()的操作變成如下;

x_count++;

if(next_count>0)

 signal(next);

else 

 signal(mutex);

wait(x_sem);//x_sem會一直等待除非signal(x_sem);

x_count--;

x.signal()的操作將變成:

if(x_count>0){

next_count++;

signal(x_sem);

wait(next);

next_count--;

}


四、原子事務

什麼是互斥鎖?簡單理解就是進入臨界區時,線程獲得鎖;退出時,線程釋放鎖。

1、系統模型

事務:執行單個邏輯功能的一組指令或操作稱爲事務。處理事務的關鍵是要保證事務的原子性。

已經成功完成執行的事務稱爲提交,否則稱爲撤銷。

若已經改變了部分內容,則要求回退機制。

以下只關心在易失性存儲上出現信息損失的情況下確保事務的原子性。

2、基於日誌的恢復

在穩定存儲上記錄有關事務對其訪問的數據所做各種修改的描述信息。最常用的方法是先記日誌後操作。日誌(這是一種數據結構)具有一下幾個內容:

事務名稱、數據項名稱、舊值、新值。

由於發現錯誤會導致檢查整個日誌,因此可以通過引入檢查點來減少開銷。

3、併發原子操作

如果多個事務併發執行,那麼相當於這些事務按任意順序串行執行,這一屬性稱爲串行化。

串行調度:每個事務原子地執行的調度。即單個事務的指令在調度中一起出現,如果有n條事務,每條指令m條指令,那麼串行調度的可能性爲n的階乘,即只和事務的條數有關。

非衝突操作:兩個操作訪問不同的數據項,這種操作是非衝突的。

衝突可串行化:如果調度s可以通過一系列非衝突操作的交換而轉換成串行調度s'。

那確保串行化的方法有哪些呢?

1、加鎖協議:爲每個數據項關聯一個鎖(兩種方式:共享(讀),排他(讀和寫))

2、基於時間戳的協議:事先在事務之前選擇一個排序順序方案。











發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章