1. 程式人生 > >CMU CSAPP筆記 第十二章

CMU CSAPP筆記 第十二章

併發的基本知識

  • 併發的應用
    • 訪問慢速I/O裝置
    • 與人互動(鍵盤輸入等)
    • 通過推遲部分工作(例如free動態記憶體)來降低延遲
    • 網路伺服器同時服務多個客戶端
  • 併發的經典問題
    • 競爭
    • 死鎖
      • A需要B執行才能執行,同時B需要A執行才能執行
    • 活鎖
      • 外部事件或者系統操作一直在子程序之前執行,導致要執行的子程序遲遲不能執行(類似於一直有人插隊)

構造併發程式的基本方法

  • 基於程序
    • 核心自動管理多個邏輯流
    • 每個程序有其私有的地址空間(也就是說程序切換的時候需要儲存和載入資料)
    • 每個客戶端由獨立子程序處理
      • 必須回收殭屍子程序
    • 子程序會複製父程序的 listenfd 和 connfd,所以在父程序中需要關閉 connfd,在子程序中需要關閉 listenfd
      • 因為伺服器只要建立一次 listenfd 即可
    • 優點在於各個程序之間獨立不會相互干擾
    • 缺點在於程序間共享資訊困難
      這裡寫圖片描述
  • 基於事件

    • 由程式設計師手動控制多個邏輯流
    • 所有的邏輯流共享同一個地址空間
    • 這個技術稱為 I/O multiplexing
  • 基於執行緒

    • 核心自動管理多個邏輯流
    • 每個執行緒共享地址空間
    • 屬於基於程序和基於事件的混合
    • 幾個相關函式
      • pthread_create
      • pthread_join
        • 阻塞並等待新執行緒執行結束,回收記憶體
        • 與waitpid不同,該函式只能等待指定的執行緒,而不能任意執行緒
      • pthread_detach
        • 使該執行緒不能被別的執行緒回收或殺死
    • 基於執行緒的伺服器在新執行緒中不用像子程序一樣關閉一個已連線描述符,因為執行緒都在同一個程序中

多執行緒中的共享變數

  • 我們說一個變數是共享的,當且僅當它的一個例項被一個以上的執行緒引用
    • 哪怕這個變數不是全域性變數,而是在某個執行緒的棧上的
      • 因為雖然每個執行緒的棧是獨立的,但是執行緒之間不設防,允許被其他執行緒訪問
  • 執行緒同步錯誤
    • 同進程一樣,執行緒的執行順序也是不固定的,也會發生競爭錯誤
    • 例如一段累加程式碼,從彙編角度來看,由開頭,尾部各一個週期,以及中間三個週期構成。而中間的三個週期的執行如果被打斷,就會出現錯誤
    • 因此需要在執行中間週期時保證不被打斷
      這裡寫圖片描述

這裡寫圖片描述

  • 進度圖
    • K個執行緒的併發可以用K維的進度圖來表示,這裡我們以最簡單的二維為例
      這裡寫圖片描述

訊號量

  • 訊號量S是一個非負整數的全域性變數,只有P,V兩種操作
    • 對於P(S),如果S是非零,P將S減一,並且立即返回;如果S為零,掛起執行緒,直到S非零,通常通過V重啟,然後P將S減一併返回
    • 對於V(S),V將S加一,但是當有多個執行緒等待重啟時,V不可預測重啟哪個
    • P和V都是不可被中斷的
    • 通過P,V可以將共享變數加鎖,避免執行緒競爭錯誤

這裡寫圖片描述

  • 利用訊號量來排程共享資源
    • 消費者和生產者問題
      • 模型
        • 生產者等待空的 slot,把 item 儲存到 buffer,並通知消費者
        • 消費整等待 item,從 buffer 中移除 item,並通知生產者
      • 應用
        • 多媒體處理
          • 生產者生成 MPEG 視訊幀,消費者進行渲染
        • 事件驅動的圖形使用者介面
          • 生產者檢測到滑鼠點選、移動和鍵盤輸入,並把對應的事件插入到 buffer 中
          • 消費者從 buffer 中獲取事件,並繪製到到螢幕上
    • 讀者和寫者問題
      • 模型
        • 讀者執行緒只讀取物件
        • 寫者執行緒修改物件
        • 多個讀者可以同時讀取物件
      • 應用
        • 線上訂票系統
        • WEB頁面快取
      • 實現策略
        • 讀者優先或寫者優先
        • 都會出現starvation的現象,即活鎖

執行緒安全

  • 4 類執行緒不安全的函式
    • 不保護共享變數的函式
      • 解決辦法:使用 P 和 V 來保護
    • 在多次呼叫間儲存狀態的函式
      • 例如rand函式,通常在傳入固定隨機數種子時,多次執行的結果是一樣的,但是rand函式的下一次輸出依賴於上一次輸出,這導致多執行緒時可能無法返回固定結果
      • 解決辦法:把狀態當做傳入引數
    • 返回指向靜態變數的指標的函式
      • 被一個執行緒使用的結果可能被另一個執行緒悄悄覆蓋
      • 解決辦法1:重寫函式,傳地址用以儲存
      • 解決辦法2:上鎖,並且進行復制
    • 呼叫執行緒不安全函式的函式
      • 解決辦法:只調用執行緒安全的函式
  • 可重入性
    • 多執行緒時不會引用任何共享資料
    • 是執行緒安全函式重要的子集,不需要同步操作,更高效
  • 標準 C 庫中的函式都是執行緒安全的(如 malloc, free, printf, scanf),大多數 Unix 的系統呼叫也都是執行緒安全的
    • 大多數不安全的函式都有可重入版本,名字為原函式名+‘_r’

死鎖

這裡寫圖片描述
這裡寫圖片描述

  • 避免死鎖的方法,對於每個執行緒呼叫P的順序一致
    • 例如都是P(S0),P(S1)的順序
    • 釋放順序隨意

這裡寫圖片描述

這裡寫圖片描述

超執行緒

這裡寫圖片描述

  • CPU在執行單執行緒任務時,並不是核心內每一個單元都在工作。而超執行緒技術就是讓閒著的那些執行單元去做另一個執行緒的工作。
    • 即共享functional units
    • 但是假設有兩個執行緒在某一時刻都要使用CPU中的一個特定執行單元,那麼他們倆就沒法同時執行了,只能一個一個來。