程序同步
引入執行緒後,我們也引入了乙個巨大的問題:即多執行緒程式的執行結果有可能是不確定的。同步就是讓所有執行緒按照一定的規則執行,使得其正確性和效率都有跡可循。執行緒同步的手段就是對執行緒之間的穿插進行控制。
鎖
兩個步驟(兩條語句)中間留有被別的執行緒穿插的空擋,可能造成執行結果的錯誤。這時,我們可以用鎖來將這兩個步驟並為乙個步驟,或者變成乙個原子操作,使其中間不留空擋。
鎖有兩個基本操作:閉鎖和開鎖。閉鎖就是將鎖鎖上,其他人進不來。開鎖就是你做的事情做完了,將鎖開啟,別人可以進去了。
閉鎖操作有兩個步驟:
①等待鎖為開啟狀態
②獲得鎖並鎖上
顯然,閉鎖的兩個操作應該是原子操作,不然就會留下穿插的空擋,從而造成功效的喪失。
鎖的實現
作業系統之所以能夠構建鎖之類的同步原語,原因就是硬體已經為我們提供了一些原子操作:中斷禁止和啟用、記憶體載入和存入、測試和設定指令。在這些硬體原子操作之上,我們便可以構建軟體原子操作。
鎖的特徵
一把正常的鎖應該具有的特徵:
①鎖的初始狀態是開啟狀態
②進入臨界區前必須獲得鎖
③出臨界區時必須開啟鎖
④如果別人持有鎖,則必須等待
把鎖機制想象成現實中的鎖,閉鎖和開鎖中間的**,想象成是放在有鎖的房間裡進行的。
缺點:
當乙個程序持有鎖時,另乙個程序只能等待(等待鎖變為開啟狀態),而這種繁忙等待就造成了浪費,也降低了系統效率。鎖的特性就是在別人持有鎖的情況下需要等待。
故:儘量減少閉鎖開鎖之間的**(一般用鎖來設定標記,保護臨界變數的操作)
有什麼辦法不用進行任何繁忙等待呢?睡覺與叫醒
睡覺與叫醒
什麼是睡覺與叫醒?就是如果鎖被對方持有,你不用等待鎖變為開啟狀態,而是睡覺去,鎖開啟後再來把你叫醒。
例:生產者與消費者問題(有問題版本)
#define n 100 //緩衝區的大小
int count = 0 ; //緩衝區中產品的數目
void producer(void)
//while}
void consumer(void)
//while
}
此**有些問題:
2, 當生產者在判斷count==1時傳送叫醒訊號給消費者,但也有可能沒有消費者在睡覺(當緩衝區為空時,恰好沒有消費者來),這個訊號放空了。沒有程序接收。同樣,消費者也存在放空訊號叫醒生產者的問題。
即訊號放空問題。可能造成死鎖。
★假定consumer先來,這個時候count==0,於是睡覺去,但是在判斷count等於0後卻在執行sleep語句前cpu發生切換。生產者開始執行,它生產一件產品後,給count加一,發現count結果為1,因此發出叫醒消費者訊號。但這個時候消費者還沒有睡覺(正準備要睡),所以該訊號沒有任何效果,浪費了。而生產者一直執行到緩衝區滿了後也去睡覺。這個時候cpu切換到消費者,而消費者執行的第乙個操作就是sleep。至此,生產者和消費者都進入睡覺狀態,從而無法相互叫醒,死鎖。
造成二者同時睡覺的原因是因為生產者發出的叫醒訊號丟失(因為消費者此時還沒睡覺)。
如果用某種方法將發出的訊號累積起來,而不是丟掉,在消費者獲得cpu執行sleep語句後,生產者在這之前傳送的訊號還保留,則消費者將馬上獲得這個訊號而醒過來。
能夠將訊號累積起來的作業系統原語就是訊號量。
訊號量
訊號量可以說是所有通訊原語裡功能最強大的。它不光是乙個同步原語,還是乙個通訊原語。而且,它還能作為鎖來使用。
訊號量semaphore實質上就是乙個計數器。其取值為當前累積的訊號數量。它支援兩個操作:加法up和減法down。
down減法操作:
①判斷訊號量的取值是否大於等於1
②如果是,將訊號量的值減1,繼續往下執行
③否則,在該訊號上等待(執行緒被掛起)
up加法操作:
①將訊號量的值增加1(此操作將叫醒乙個在該訊號上等待的執行緒)
②執行緒繼續往下執行
注意,down和up操作雖然包含多個步驟,但這些步驟是一組原子操作,它們之間是不能分開的。
down和up也被稱為p、v操作。p和v是荷蘭語中的減少、增加的意思。
如果我們將訊號量的取值限制為0和1兩種情況,則我們獲得的就是一把鎖,也稱為二元訊號量
二元訊號量down減法操作:
①等待訊號量取值變為1
②將訊號量值設定為0
③繼續往下執行
二元訊號量up加法操作:
①將訊號量值設定為1
②叫醒在該訊號量上面等待的第1個執行緒
③繼續往下執行
二元訊號量的取值只有0和1,它可以防止任何兩個程式同時進入臨界區。
★二元訊號量具備鎖的功能,down就是獲得鎖,up就是釋放鎖。但它又比鎖更為靈活:因為等在訊號量上的執行緒不是繁忙等待,而是去睡覺,等另外乙個執行緒執行up操作來叫醒。因此,二元訊號量從某種意義上說就是鎖和睡覺與叫醒兩種原語操作的合成。
例:用訊號量解決生產者和消費者問題。
(訊號量:就是 資源數+睡覺喚醒機制)
#define n 100 //定義緩衝區大小
typedef int semaphore;
semaphore mutex = 1; //互斥訊號量
semaphore empty = n; //緩衝區計數訊號量,用來計數緩衝區裡的空位數量,初始允許n個生產者生產
semaphore full = 0; //緩衝區計數訊號量,用來計數緩衝區裡的產品數量
void producer(void)}
void consumer(void)
}
生產者和消費者等待的訊號不同,它們需要睡在不同的訊號上,故設定乙個full和乙個empty來分別記錄。
鎖、睡覺與叫醒、訊號量
鎖解決了同步問題,但帶來的是迴圈等待。為了消除迴圈等待,我們發明了睡覺與叫醒。但睡覺與叫醒又帶來了死鎖,因此我們發明了訊號量。
(p、v操作把睡覺與叫醒的if(……) sleep ; cnt++; 語句結合在一起成原子操作,消除了語句之間的空擋)
(睡眠與喚醒是直接針對關聯的程序操作的,而訊號量機制是針對訊號量操作的,程序在相應訊號量上獲取、增添,即不針對某一程序傳送訊號。)
(執行緒同步的** 不要有判斷狀態標記的if語句,最好把這些狀態轉換成訊號量,讓訊號量操作的系統原語去判斷,做出反應。這樣可防止訊號丟失,降低死鎖的可能。)
使用訊號量原語時,訊號量的操作順序至關重要。稍有不慎,就可能發生死鎖。
管程
為解決訊號量使程式編寫困難、程式效率低下的問題,我們將這些組織工作交給乙個專門的構造來管,則程式設計師就解脫了。管程即監視器的意思。它監視的是程序或執行緒的同步操作。具體說來,管程就是一組子程式、變數和資料結構的組合。把需要同步的**用乙個管程的構造框起來,將需要保護的**置於begin monitor 和 end monitor之間,即可獲得同步保護。編譯器來保證它的正確執行。
管程最大的問題就是對編譯器的依賴。我們需要編譯器將需要的同步原語加在管程的開始和結尾。實際上,多數的程式語言也並沒有實現管程機制。
訊息傳遞
訊息傳遞是通過同步雙方經過互相收發訊息來實現。它有兩個基本操作,傳送send和接收receive。它們均是作業系統的系統呼叫,而且既可以是阻塞呼叫,也可以是非阻塞呼叫。
--send(destination, &message)
--receive(source, &message)
例:使用訊息傳遞實現 生產者與消費者同步
#define n 100
void producer(void)}
void consumer(void)
{ int item ;
message m ;
for (i=0;i
同步需要的是阻塞呼叫。即如果乙個執行緒執行receive操作,就必須等待收到訊息後才能返回,也就是說,如果呼叫receive,則該執行緒將被掛起,在收到訊息後,才能轉入就緒。
生產者每生產一件產品,就需要從消費者那裡獲取乙個空盒子,然後將產品裝進盒子裡,再把裝了產品的盒子傳送給消費者。消費者的工作過程剛好反過來。生產者和消費者就這樣通過訊息的傳遞進行同步,既不會死鎖,也不會繁忙等待。而且無需使用臨界區等機制。
(訊息傳遞機制與訊號量不同,它是直接與相關執行緒通訊,而訊號量機制是不同訊號量之間互動 程序掛靠在不同的訊號上)
訊息傳遞還可以跨計算機進行同步,即可以對處於不同計算機上的執行緒實現同步。故,訊息傳遞是當前使用非常普遍的執行緒同步機制。
作業系統 程序同步
臨界資源 critical resouce 臨界區 critical section 硬體同步機制 訊號量機制 訊號量的應用 管程3使用多道批處理系統不僅能有效的改善資源的利用率,還可以顯著地提高系統的吞吐量,但同時會使系統變得更加複雜,會使程式的執行結果存在不確定性。所以必須引入程序同步機制從而保...
作業系統 程序同步
引入程序 提高了資源的利用率和系統的吞吐量 程序的非同步性 會給系統造成混亂 程序同步基本概念 1,兩種形式的制約關係 a 間接相互制約 ab兩程序爭用一台印表機 b 直接相互制約 a程序放資料 緩衝區 b程序從緩衝區取資料 2,臨界資源 硬體臨界資源 軟體臨界資源 印表機,磁帶機,緩衝區。3,臨界...
作業系統 程序同步
ipc.件 include include include include include include include define bufsz 256 建立或獲取 ipc 的一組函式的原型說明 int get ipc id char proc file,key t key char set s...