1. 程式人生 > >Linux下的同步與非同步

Linux下的同步與非同步

       由於程序的概念導致,獨立討論一個程序是毫無意義的。而多個程序在工作時,如果這些程序之間存在一定聯絡(當然可能毫無聯絡),則此時會有兩種情況。這就是本文要討論的。同步和非同步。

        在我初學階段,一直以為同步是同時進行的。這是個錯誤的概念。曾經看到有人舉例,打電話是同步,相對於發訊息,我覺得似乎不妥。打電話,如果你說你的,我說我的,則就不是同步,是標準的非同步模式,而發訊息,比如簡訊,一問一答,OK,這是標準的同步模式。有人說,阻塞是同步模式,非阻塞是非同步模式,這樣的解釋似乎有點問題。同步不一定需要阻塞。自然非阻塞也不一定是非同步。
         那麼,談論程序之間的同步和非同步的最根本的區別是什麼呢?我用個文縐縐的描述如下:
     當一個程序A存在一個必須執行的操作點,同時該操作點,與另一個程序B的某個操作點存在因果時序關係,則A相對B為同步。反之為非同步。

這裡需要注意幾點,在其他討論同步和非同步問題中所沒有或完全不一的概念:
1、A相對B同步,未必B相對A同步
         最明顯的例子是,客戶段請求和對反饋的處理是相對於服務端同步的。在服務段接受客戶段資訊後,到服務段資料沒有完全給予客戶端之前,這段時間客戶端需要等待,也就是阻塞,以同伺服器端運行同步。而伺服器端並不存在這個問題。如果這個客戶端不給伺服器傳送請求,伺服器會轉向響應其他客戶端的請求。則伺服器端相對客戶端並不是同步的。
之所以出現這種不對稱的更本原因,是A,B程序工作目標,或工作性質決定的。
2、AB兩個程序之間必須存在操作點之間的因果關係,才叫同步。如果說兩個程序搶印表機,則不叫同步。雖然可能存在某個操作點上的相互影響。雖然他們之間可能出現阻塞。但和同步非同步沒聯絡。
這一條,實際上也是說擴充套件說明了。討論到同步非同步,均是指:AB協同完成一個整體任務,缺一不可的情況下。如果兩個毫無聯絡,毫無因果關係,老死不相往來的程序,則不存在同步和非同步的討論。雖然你可以說他們是絕對非同步。但沒有必要通過同步和非同步的分析方法來處理和約束這兩個程序的設計。
3、A相對B同步,則一定存在一個嚴格按照{ 請求-->等待-->接受 }的時序過程進行。
這種工作在軟體設計時是顯式存在的(即你在程式碼中可以看到)。也就是說,存在因果關係的阻塞才是同步。
例如,機器A向機器B傳輸資料。採用對資訊分塊,並打包,包字首含有同步資訊的時候,或許有人說。此時不需要等待,因為存在同步頭。這種觀點本身沒有錯。如果僅放在傳輸之段執行時間內來看。但考慮一下傳輸前的工作。機器A可以在任何情況下,直接傳遞資訊嗎?不需要建立通道(通過握手,或查詢緩衝區是否清空的各種方式)?考慮一下握手動作,A傳送請求REQ,B接受,返回ACK。這就是請求,等待,接受,而採用緩衝方式,A要查詢緩衝區是否空(即上一次資訊是否被B全部獲取),這實質是個等待過程,而發現緩衝區為空,這實質是通過緩衝區的情況,來實現機器B向機器A傳送接受資訊。
4、同步和非同步都是相對的。在不同的尺度下,可能產生變化。
而同步程式碼的設計和非同步程式碼的設計存在本質差異,因此,分割尺度形成模組化設計,才是程式設計的大學問。至於採用什麼函式,什麼呼叫方式,這本身不是一個程式設計師的價值所在。你能背下所有的C的標準庫的函式及參量不代表你能成為一個合格的程式設計師。

     分析同步和非同步,既要考慮任務切割後,每個子部分的工作特性,又要考慮每個子部分及整體的執行引數和目標要求。

我們做個例子來分析同步和非同步

      程序A完成外部鍵盤讀入,並存在一個緩衝區。而對讀入的字元的分析,由程序B執行。當緩衝區滿時,一次性傳遞給B,由B處理。雖然你按一個鍵可以在任意時間發生,但A的緩衝區的內容必須等滿後才能傳遞給B,而同時,只有B不忙時,才能接收資料。如果A的緩衝區滿了。必須等待B接收完資料,才能繼續執行。因此從整體上來看,A相對B而言是同步的。
     可以反證,假設A 相對B是非同步的,則A的執行和B沒有關係。但是,A在緩衝區滿時,無法繼續執行,因此假設不成立。
     但B相對A是同步嗎?
假設B的工作如下:
      如果鍵盤請求存在,則接收鍵盤資訊處理(這和上面的工作是一致的),但如果鍵盤沒有資訊傳遞,則檢測滑鼠是否存在資訊傳入。此時,B程序,並不會因為A的資料沒到位而停止工作。也就是說。無論A是什麼情況,B還有很多其他事情要做。同時B該做什麼做什麼,等有空了,在看是否A有資料需要處理。
因此,B的工作,不會因為A的狀態,而導致阻塞。


     上面的討論,說明了。同步不是相互依賴存在的。

     那麼將上面的例子修改一下,假設A的緩衝區只要有資料,就傳遞給B操作。同時假設緩衝區足夠大,B的處理時間足夠快。那麼此時,A相對B又變成了非同步。為什麼呢?
      因為,B在處理A緩衝區的內容的時間足夠快,以至於A不會因為外部鍵盤輸入把緩衝區填滿導致A的停滯。可以說,A的工作和B沒有關係。B的工作和A也沒有關係。
     可能有人會說,上面例子,一會是同步,一會是非同步,其根本原因在於,A緩衝區是否填滿才向B傳遞資料。如果這樣理解,則是不完備的。如果這麼去設計工程,必然會導致一個潛在風險(假設當作非同步模式設計,但A被填滿同時阻塞,由於認為是非同步模式,所以A採用丟後不管的方式,而阻塞下,A會漏掉輸入資訊,導致系統出錯)。
實際上,上面的同步和非同步的差異,還存在於一個假設,即B處理的速度足夠快,A的緩衝區足夠大。這種情況下,才能完全認為A,B是非同步模式。當你發現你的工程的使用目標不能滿足上面的假設(B處理速度足夠快),則一定要小心處理,當緩衝溢位的程式碼。按照同步設計的思想進行處理。

     討論到這裡,你也可以發現,絕對同步是存在的,絕對非同步是不存在的。除非兩個任務絲毫沒有聯絡。但不代表非同步工作毫無意義。UDP與TCP就可以看作,在針對訊息傳遞這個工作上,前者是非同步模式,後者是同步模式。很多情況下,無法做到完全同步(這需要硬體支援),也有些情況下,不需要做到完全同步(例如廣播訊息,沒有收到的資訊,並不是重要的,不會對系統產成影響)
      下面討論一下同步和非同步在設計,或規劃中的問題。
      首先,我們假設用模組來替代程序。這樣可以更好的討論問題。那麼模組的劃分,直接影響到屬於同步還是非同步。
例如,上面的例子,假設A程序實際上內部有兩個模組,一個模組是讀取鍵盤訊號,轉換成字元。而另一個是管理緩衝區,並向B傳送訊息。
       整體看程序A相對B是同步模組。但內部讀取鍵盤訊號的工作和緩衝區管理則是非同步的。那麼是否可以將兩個模組合併,並形成一個程式程式碼依次執行呢?假設硬體不存在緩衝,通過鍵盤中斷獲取訊息。這樣完全可以。但是這樣會使得程式碼邏輯混亂。因為,可能處理緩衝區的工作中,被鍵盤中斷資訊打斷。這樣會有一個可能的錯誤。
我們假設BUF足夠大,傳遞資料足夠快,同時我們在A模組中,採用雙BUF方式,確保任意按鍵被記錄。則通常存在如下程式碼

   if (buf.size >=MAX_BUF_SIZE){//當緩衝區滿
   switch_buf(buf,send_buf);//將接受緩衝區和寫出緩衝區進行切換。
    buf.size =0;//A的緩衝區清0
   send_data(send_buf);//向B傳送資料,這裡面假設最簡單的傳送方式,B存在個對等的緩衝區,而只是個簡單的COPY工作。同時不考慮B的緩衝區的資料衝突問題。
   }
  如果中斷髮生在switch_buf(buf,send_buf);之後,buf.size = 0;之前,很顯然,通過
    buf.data[buf.size ] = c;的方式記錄按鍵會出錯。當然上述程式碼可以修改成如下模式
   if (buf.size >=MAX_BUF_SIZE){
      switch_buf(buf,send_buf);
      send_data(send_buf);
      send_buf.size = 0;
   }
      那麼如果中斷髮生在if (buf.size >= ...)之後,switch_buf之前,或switch_buf內部,如何處理呢?通常系統設計,為了杜絕這種問題,採用了原子操作,其實就是關中斷,處理些事情,再開中斷。保證這些事情在處理時中斷禁止執行。則上面程式碼得修改如下
   cli();//關中斷
   if (buf.size >=MAX_BUF_SIZE){
         
      switch_buf(buf,send_buf);
   sti();//開中斷
      send_data(send_buf);
      send_buf.size = 0;
   }else{
      sti();//開中斷
   }

       這樣的寫法,保證了模組A的正確性。但是這樣的系統設計有些不妥(得承認設計本身沒有問題,執行也沒有問題),不妥在哪?cli和sti因為什麼而導致要關閉?寫程式碼的人和系統規劃作設計書的人可能知道,但後者很難詳細描述這個細節。看程式碼的人,則可能一頭霧水。甚至在新增,調整程式碼時,將這個正確設計給改錯了。那麼不妨如下設計。
   if (buf.size >=MAX_BUF_SIZE){
    g_switch =1;
      switch_buf(buf,send_buf);
    if (back_c){
      buf.data[buf.size ] = back_c;back_c =0;
    }
      g_switch = 0;
      send_data(send_buf);