1. 程式人生 > >那些煩人的同步和互斥問題

那些煩人的同步和互斥問題

真正的知識是深入淺出的,碼農翻身” 公共號將苦澀難懂的計算機知識,用形象有趣的生活中例項呈現給我們,讓我們更好地理解。

1、批處理和離線列印

印表機程式,準確的說是印表機程序,在這個批處理系統中生活得非常自在,它所在的機器叫做IBM1401,除了列印之外什麼也不幹,每天大部分時間都是歇著。

這個系統還有兩臺機器,一臺還是IBM1401,它專門收集程式設計師寫出來的穿孔卡片,然後轉成磁帶。  然後,操作員把磁帶輸入到IBM7094這個昂貴又強大的計算機上執行,執行結果也會輸出到磁帶上。 最後磁帶被拿到1401上進行列印, 這叫做離線列印(不和7094相連線)。如下圖:

圖一、離線列印

在沒有磁帶來的時候,印表機程式無所事事,這就是離線列印的好處。更大的好處是,磁帶上需要列印的東西都是順序的,一個接一個列印就可以了,完全沒有衝突的問題。這是沒辦法的事情,那時候的計算機,尤其是IBM 7094太過昂貴,要充分的利用它的每一分每一秒,然後就想出了這樣一個收集程式,然後成批處理的點子。 

2、假離線列印

隨著計算機系統的發展,列印程式的好日子很快就結束了,電腦越來越便宜,最後每個人的桌子上都有一臺電腦了。個人電腦的計算能力更是強大的驚人,列印程式也被整合進了個人電腦裡,和其他各種各樣的程式生活在一起。列印的需求仍然很強烈,像Word、WPS、Excel 、 IE、Chrome...這些程式時不時都要列印,這時候衝突就會產生。 因為只有一個印表機,到底先列印誰的文件就是個大問題。最後作業系統老大想了個辦法,專門開闢了一塊空間,誰要想列印的話就按照先來後到的次序排隊放在那,原來的列印程序變成了一個列印守護程序,會週期性的檢查是否有檔案列印,如果有則取出隊伍排頭的,打印出來,然後刪除佇列中檔案。列印程序覺得這和原來的離線列印很像,只不過用一個佇列替換了原來的磁帶,所以就叫做假離線列印

圖二、假離線列印

3、衝突

但是這個佇列可不是原來的磁帶了,它完全是個動態變化的東西,試執行還不到20秒,衝突就出現了。 

WPS氣沖沖的來著印表機程序:“印表機,你怎麼搞的?我的放假通知.wps 為什麼沒有列印?” 

印表機:“我沒看到什麼放假通知.wps啊!”

WPS:“我明明放在了編號為3的槽裡,怎麼可能沒有了?”

在作業系統老大的協助下,大家查了半天,才知道是Word引起的:

當時Word 插了一腳,也進來列印,讀到了in = 3,就是說佇列中編號為3的槽是空著的,他把3這個值放到了自己的區域性變數free_slot中,這時候發生了一次時鐘中斷,作業系統老大認為Word已經運行了足夠長的時間,決定切換到WPS程序。 

WPS也讀到了in = 3,把3 也存到自己的區域性變數free_slot中,現在Word和WPS都認為下一個空的槽是3!

WPS接著幹活,他把檔案放到了第3號槽裡,並且把in 改為4,然後離開了。接下來又輪到Word運行了,它發現free_slot 為3,就把檔案也放到了第3號槽裡,把free_slot 加1,得到4,存入in 中。可憐的WPS,他的檔案被覆蓋掉了。

但是印表機程式啥也察覺不出來,照樣列印不誤。  

圖三、衝突

4、臨界區

很明顯,Word和WPS 這兩個程序甚至多個程序在讀寫in這個共享變數的時候,最後的結果嚴重依賴於程序執行的精確次序,這次是WPS的檔案被覆蓋掉了,下次可能就是Word了。 
這種對共享變數,共享記憶體,共享資源進行訪問的程式片段叫做臨界區。程式碼在進入臨界區之前一定要做好同步或者互斥的操作。
WPS說:“老大,當時你切換Word的時候是不是發生了一次時鐘中斷 ?”
作業系統:“是啊,有了時鐘中斷我才能計算時間,然後做程序切換啊!”
“那在訪問這個in共享變數的時候,我們自己能不能把這個中斷給遮蔽?這樣就不會有程序切換,肯定沒問題了。” Word問道。
“你想的美,時鐘中斷是最基本的東西,我把這個許可權給了你們應用程式,到時候那個傢伙遮蔽以後忘記開中斷,我們整個系統就要完蛋了!” 作業系統狠狠的瞪了Word 一眼,Word趕緊噤聲。 
“不過我聽說有些機器提供了一個特別的指令,這個指令能檢查並且設定記憶體的值,而不會被打斷,叫做TestAndSet,如果用C語言描述的話,類似這樣:”

bool TestAndSet(bool *lock){
  bool rv = *lock;
  *lock = true;
  return tv;      
}

“需要注意的是,這個函式中的三條指令是“原子”執行的,也就是說不會被打斷。你們要是想用的話可以這樣用:”

bool lock = false;
while(TestAndSet(&lock)){
  ;//什麼也不做  
}

臨界區;
lock = false;
剩餘區;

WPS說:“看起來有點複雜,讓我想想啊,我和Word 的臨界區程式碼,就是‘訪問in變數,放入待列印檔案,然後把in 加1’ 。那在進入臨界區之前,我們倆都會呼叫TestAndSet。如果是我先呼叫,lock會被置為true,函式就會返回false。我就跳出了迴圈,可以進行後續臨界區操作了,而Word 在呼叫 TestAndSet的時候,函式一直返回true,他只好不停的在這裡迴圈了。”
“是啊!”,Word 接著說,“我會不停的迴圈,直到WPS 離開臨界區,然後把lock置為false。 ”
“這個方法看起來很簡單啊,只要一個變數加上一個函式就能讓我和Word 進行互斥操作。”
作業系統說: “是的,實現了你們兩個的互斥,但是並不是所有的機器都會提供這樣的指令,所以也不通用。”

5、生產者-消費者

印表機程序說:“你們討論了半天,只是解決了兩個程序往佇列裡放檔案的衝突問題,現在也得考慮考慮我了。”
“有你啥事?”,Word和WPS 都不以為然。
“你們想想,那個列印佇列對5個‘槽’,要是滿了就沒法往裡邊放了,你們都得等;要是空了,我就得等你們往裡邊放東西,所以咱們之間是不是也得同步?”
“這就是所謂的生產者和消費者問題,也是個老大難問題了”,老大總結道。 
“那用剛才那個鎖好像不行啊,它能搞定互斥,但是做多個程序的同步就有點力不從心了。”
作業系統老大說:“聽說荷蘭有個叫Dijkstra的,發明了一個訊號量(semaphore)的東西,能解決這個問題。”

 

圖四、科學家Dijkstra.

“訊號量是什麼鬼?訊號燈嗎? ”
"所謂訊號量,說白了其實就是一個整數,基於這個整數有兩個操作:wait 和 signa。”

int s;
wait(s){
  while(s <= 0){
        ;//什麼也不做
    }  
    s--;
}        

signal(s){
  s++;  
}

“這....這....這是啥玩意兒,這麼簡單,能解決啥問題?再說了你看看這s++、s--和我們佇列中的in、out不是一樣嗎?在多程序切換下自身正確性都難保,還能解決別人的問題?” ,WPS吃驚的問。
“WPS 問的好啊,說明他思考了,實際上這個東西必須得我出馬來實現”,作業系統老大說,“ 我會在核心實現wait 和signal,讓你們呼叫,比如我在做s++、s-- 時,我可以遮蔽中斷。”
 Word說:“這個簡單的小東西有點意思,比如我們倆可以用它做互斥:”

int lock = 1;
wait(lock);
臨界區;
signal(lock);
剩餘區;

列印程序說:“既然訊號量是個整數,也許可以解決我們消費者-生產者直接的同步問題。”

int lock = 1;
int empty = 5;
int full = 0;

生產者:
while(true){
  //如果empty的值小於等於0,生產者只好等待
  wait(empty);
  //加鎖(因為要操作佇列,和其它生產者互斥)
  wait(lock);
  把新產生的檔案加入佇列;
  //釋放鎖
  signal(lock);
  //通知消費者佇列中已經產生了新的檔案
  signal(full);     
}

消費者:
while(true){
  wait(full);
  wait(lock);
  把佇列頭的檔案列印,刪除;
  signal(lock);
  signal(empty);
}

Word說:“我的天,真是複雜啊,容我想想,我和WPS都是生產者。假設我們倆都開始執行生產者程式碼,先去wait(empty),發現沒有問題,因為empty的初始值為5。接下來都去執行wait(lock),這時候就看誰先搶到了。如果我先搶到,我就可以往佇列里加檔案,然後釋放鎖,WPS就可以接著放檔案了。最後我還要把full這個值加一,目的是印表機程序可能在等待。恩,看起來不錯!”
作業系統老大說:“是啊!在多程序下,由於程序的執行隨時都有可能被打斷,還要保證正確性,不能出一點閃失。這對程式設計師的挑戰很大,出現了疏漏,很難定位。”
列印程序說:“老大,我注意到wait函式中,如果s 的值 為0或小於0 ,那個while 迴圈會一直執行,CPU豈不是一直在忙等?”
“確實是這樣,我們改進下,讓忙等的程序進入休眠吧,很明顯,這件事還得我做啊”, 作業系統說道。

//把整數型訊號量封裝成一個結構體
typedef struct{
  int value;
  struct process *list;//process程序就緒佇列
}semaphore;

wait(semaphore *s){
  s->value--;
  if(s->value < 0){
    把當前程序加到s->list中;
    block();//把程序休眠,放棄cpu
  }
}

signal(semaphore *s){
  s->value++;
  if(s->value <= 0){
    從s->list中取出一個程序p
    wakup(p);//喚醒程序p
  }
}

WPS說:“唉,真是好複雜!不過我想起一個問題,這些wait、signal 能用到我們內部的執行緒的同步上嗎?”
“當然可以,概念上是一致的,都是訪問共享資源的程式,需要做同步和互斥操作,可能表現形式不同。”
“難道那些程式設計師們真的要使用這些wait、signal 程式設計嗎?多容易出錯啊!”
“一般來說,程式設計師們所使用的工具和平臺會做抽象和封裝,例如在Java JDK中,已經對執行緒的同步做了封裝了,對於生產者-消費者問題,可以直接使用BlockingQueue。非常簡單,完全不用你去考慮這些wait、signal、full、empty。”

//建立一個佇列,其中佇列中已經對wait和signal操作做了封裝
BlockingQueue queues = new LinkedBlockingQueue(10);

生產者:
//如果佇列滿,執行緒自動阻塞,直到有空閒位置
queues.put(xxx);

消費者:
//如果佇列空,執行緒自動阻塞,直到有資料到來
queues.take();

WPS說:“果然是抽象大法好,這多簡單啊。”

作業系統說:“是啊!無論是什麼東西,抽象以後用起來好多了。但是還是要了解底層,這樣出現了類似於BlockingQueue這樣的新概念, 你能迅速搞明白。”

(完)

“碼農翻身” 公共號:由工作15年的前IBM架構師建立,分享程式設計和職場的經驗教訓。

長按二維碼, 關注碼農翻身

相關推薦

那些同步互斥問題

真正的知識是深入淺出的,碼農翻身” 公共號將苦澀難懂的計算機知識,用形象有趣的生活中例項呈現給我們,讓我們更好地理解。 1、批處理和離線列印 印表機程式,準確的說是印表機程序,在這個批處理系統中生活得非常自在,它所在的機器叫做IBM1401,除了列印之外什麼也不幹,每天大部分時間都是歇著。 這個系統還有兩臺

線程同步互斥的區別

之間 方法 無法 spa lock zed 模式 col 一定的 互斥是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。 同步是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。

python-Event事件線程同步互斥

sse logs pan else 控制 事件 utf-8 event Coding 1 #!/usr/bin/python 2 #coding=utf-8 3 #用於線程間通信,通過事件標識控制 4 import threading 5 from time

python-Lock鎖線程同步互斥

() print b+ != val threading true pytho color 1 #!/usr/bin/python 2 #coding=utf-8 3 #線程間通信的同步與互斥操作-鎖 4 import threading 5 a=b=0 6

todo:進程的同步互斥

style tro 生產 哲學家 問題 str 同步 消費 哲學家進餐問題 1、生產者--消費者問題 2、讀者--寫者問題 3、哲學家進餐問題todo:進程的同步和互斥

程序同步的基本概念:臨界資源、同步互斥

在多道程式環境下,程序是併發執行的,不同程序之間存在著不同的相互制約關係。為了協調程序之間的相互制約關係,引入了程序同步的概念。 臨界資源 雖然多個程序可以共享系統中的各種資源,但其中許多資源一次只能為一個程序所使用,我們把一次僅允許一個程序使用的資源稱為臨界資源。許多物理裝置都屬於臨界資源

那些的廣告,滾出我們的APP!

1-現像 最近越來越多的使用者向客服反映,在使用我們的App過程中,會莫名其妙的在底部出現廣告,甚至黃色資訊。這些資訊不但困擾了使用者,影響使用者的使用,最關鍵是使得使用者散失對我們公司的信任,覺得我們公司的開發技術不行(什麼???什麼?技術不行??出

執行緒間機制(同步互斥

執行緒間機制 { 多執行緒共享同一個程序子地址空間 優點: 執行緒間很容易進行通訊 1、 通過全域性變數實現資料共享和交換 缺點: 多個執行緒同時訪問共享物件時需要引入同步和互斥機制 }

三十九、Linux 執行緒——執行緒的同步互斥

39.1 概念 執行緒同步 是一個巨集觀概念,在微觀上包含執行緒的相互排斥和執行緒先後執行的約束問題 解決同步方式 條件變數 執行緒訊號量 執行緒互斥 執行緒執行的相互排斥 解決互斥的方式

三十九、Linux 線程——線程的同步互斥

cdi 案例 turn size 文件替換 .com pwd || col 39.1 概念 線程同步 是一個宏觀概念,在微觀上包含線程的相互排斥和線程先後執行的約束問題 解決同步方式 條件變量 線程信號量 線程互斥 線程執行的相互排斥 解決互斥的方式 互斥

多執行緒同步互斥有哪幾種實現方法?

執行緒間的同步方法大體可分為兩類:使用者模式和核心模式。顧名思義,核心模式就是指利用系統核心物件的單一性來進行同步,使用時需要切換核心態與使用者態,而使用者模式就是不需要切換到核心態,只在使用者態完成操作。使用者模式下的方法有:原子操作(例如一個單一的全域性變數),臨界區。核

淺談程序同步互斥的概念

簡介     程序同步是一個作業系統級別的概念,是在多道程式的環境下,存在著不同的制約關係,為了協調這種互相制約的關係,實現資源共享和程序協作,從而避免程序之間的衝突,引入了程序同步。 臨界資源     在作業系統中,程序是佔有資源的最小單位(執行緒可以訪問其所在

程序同步互斥的關係

程序的同步和互斥是併發程序的兩種重要的關係,程序互斥反映了程序間的競爭的關係,程序同步反映了程序間協作的關係,從以上對程序互斥和同步的分析中,程序互斥其實就是一種特殊的程序的同步,例如,程序的互斥是

Linux執行緒淺析[執行緒的同步互斥之執行緒互斥鎖]

Linux執行緒淺析[執行緒的同步和互斥] 執行緒同步 執行緒互斥 執行緒互斥的相關函式 執行緒同步的相關函式 執行緒同步 是巨集觀上的一個概念,在微觀上面包含執行緒的相互排斥和執行緒的執行順序的約束問題 解決方法: 條件變數 執行

同步互斥——生產者消費者程序版

一、同步和互斥的概念<?xml:namespace prefix = o /> 什麼是同步?什麼是互斥? 同步是一種時序關係。如規定了程序1處理完事情A後,程序2才能處理事情B,經典的同步問題是生產者和消費者間的同步. 互斥描述的是一種獨佔關係.如任一時刻,進城1

程序間同步互斥

《程式設計思想之多執行緒與多程序(1)——以作業系統的角度述說執行緒與程序》一文詳細講述了執行緒、程序的關係及在作業系統中的表現,《程式設計思想之多執行緒與多程序(2)——執行緒優先順序與執行緒安全》一文講了執行緒安全(各種同步鎖)和優先順序,這是多執行緒學習必須瞭解的基礎。本文將接著講一下C++中多

並行、併發、同步互斥

並行 計算機作業系統中的並行,指的是同時存在於記憶體中的多道作業都處於執行狀態。實際上都是巨集觀上並行,微觀上序列,因為這些作業都是開始各自的執行,但都沒執行完畢,只是交替地使用cpu。     在作業系統中是指,一組程式按獨立非同步的速度執行,不等於時間上的重疊(同

介紹Linux下面執行緒的操作、多執行緒的同步互斥

 執行緒?為什麼有了程序還需要執行緒呢,他們有什麼區別?使用執行緒有什麼優勢呢?還有多執行緒程式設計的一些細節問題,如執行緒之間怎樣同步、互斥,這些東西將在本文中介紹。我在某QQ群裡見到這樣一道面試題: 是否熟悉POSIX多執行緒程式設計技術?如熟悉,編寫程式完成如下功能

警察叔叔就是它!那些的空格!

網頁中空格的煩惱 相信認真做過網頁設計(Web Design),或者關注過頁面設計的同行都對“空格”不陌生。“空格”屬於一類細節,很容易被忽略,但又比較影響效果,更麻煩是很不好控制。 就所提問題而言,創優翼小編我目前還是堅定的“不空格”派系。我不否認“空格”後效果的提升,

【Java併發基礎】併發程式設計領域的三個問題:分工、同步互斥

前言 可以將Java併發程式設計抽象為三個核心問題:分工、同步和互斥。 這三個問題的產生源自對效能的需求。最初時,為提高計算機的效率,當IO在等待時不讓CPU空閒,於是就出現了分時作業系統也就出現了併發。後來,多核CPU出現,不同的任務可以同時獨立執行,於是就出現了並行【分工】。有了分工後,效率得到了很大的提