1. 程式人生 > >為什麼多執行緒是個壞主意

為什麼多執行緒是個壞主意

原文地址:

Unix程式設計藝術 中,提到了儘量避免多執行緒程式設計模型, 認為這樣只會增加複雜度, 提倡使用多程序, 這樣本質上就可以避免多執行緒『共享記憶體資料』產生的 “corruotped memory” 問題。

其中, 提到了一篇文章 Why Threads Are A Bad Idea, 對於多執行緒程式設計和事件程式設計分析的非常好, 具體的翻譯如下:

1 介紹

執行緒的背景:
  • 在作業系統中出現多執行緒
  • 逐漸演變成 使用者層面的程式設計工具
  • 被認為是多種問題的一種通用解決方案
  • 每一個程式設計師都需要成為 一個多執行緒程式設計的高手嗎?
根本性的問題:

多執行緒的程式非常難以正確的編寫!!!

替代性的方案:

使用事件驅動的程式設計方法

特別宣告:
  • 對於大部分的多執行緒程式,使用事件驅動是一個更好的選擇
  • 只有當使用CPU多核的時候, 才需要使用多執行緒程式設計

2 多執行緒的本質

多執行緒程式設計

  • 一般用來管理併發問題
  • 多個獨立相互執行的任務
  • 共享的記憶體
  • 預先的安排機制(Pre-emptive scheduling)
  • 同步機制(synchronization)

3 多執行緒的用途

  • 作業系統: 對每一個使用者程序分配一個核心執行緒
  • 科學應用程式: 每個CPU分配一個執行緒(對計算要求性很高的程式)
  • 分散式系統: 程序請求並行(同步記性的I/O操作)
  • GUIs程式
    • 執行緒對應使用者的行為. 在長時間的後臺計算過程中仍然可以處理圖形展示
    • 多媒體, 動畫方面的程式編寫

4 多執行緒有什麼問題?

痛苦指數

  • 對於一般的程式設計師而言,難以掌握。
  • 即使對於專家,多執行緒程式設計也是痛苦的。

5 為什麼多執行緒程式設計很難?

  • Synchronization(同步機制):
    • 必須通過鎖來共享資料
    • 忘記了加鎖?就會導致受汙染的資料
  • 死鎖
    • 依賴鎖,會導致迴圈依賴
    • 每個處理程式等待其他處理程式: 導致系統掛起

系統掛起

6 為什麼多執行緒程式設計很難?

  • 難以除錯: 因為 資料依賴,時間依賴
  • 執行緒破壞了抽象: 無法設計出模組化的程式
  • 因為鎖導致回撥無法完成

多執行緒

7 為什麼多執行緒程式設計很難?

  • 很難達到非常好的效能
    • 簡單的鎖導致了低併發
    • 而精密的鎖又會導致複雜度提升, 降低了一般情況下的效能
    • OSes限制了效能提升(排程, 環境切換)
  • 執行緒不受支援
    • 難以支援多執行緒程式碼(mac, windows)
    • 一些標誌庫不是執行緒安全的
    • 核心呼叫, windows系統不是多執行緒
    • 很少有多執行緒程式設計的除錯工具
  • 通常不需要併發場景

8 時間驅動程式設計

  • 一個執行流程序: 沒有CPU的併發
  • 在時間上註冊訊息(通過回撥)
  • 事件輪詢等待訊息, 呼叫處理器模型
  • 時間處理器沒有搶斷
  • 處理器通常是 短生命週期的

事件驅動

9 事件驅動程式設計被用來幹什麼

  • 大多數的GUIs程式設計:
    • 一個處理器對應一個事件
    • 處理器用來執行行為(撤銷,刪除檔案等)
  • 分散式系統
    • 一個處理器用來對應一個輸入源
    • 處理進來的請求,返回結果
    • 事件驅動的I/O 來處理 I/O併發

10 事件驅動程式設計的問題

  • 長時間執行的時間處理器會導致 程式沒有反應, 解決辦法:
    • 對於長時間執行的程式Fork off子程式處理, 當處理結束後使用事件
    • 打斷處理器執行(比如: 事件驅動的I/O)
    • 定期回撥 時間處理器中的 事件迴圈
  • 通過處理器無法維護本地記憶體狀態(處理器必須返回)
  • 沒有CPU的併發(不太合適科學計算程式)
  • 事件驅動的程式設計並不總是被支援

11 多執行緒程式設計 VS 事件驅動程式設計

  • 事件驅動編髮程式設計儘可能的避免 併發, 而多執行緒程式設計則傾向於併發:
    • 使用事件驅動程式設計更加容易: 不用考慮併發, 不用考慮搶佔, 不用考慮同步和死鎖
    • 只在特定的情況下,才使用複雜的技術棧
    • 使用多執行緒程式設計, 即使最簡單的程式也需要面對很高的複雜度(full complexity)
  • 使用事件驅動更加容易除錯
    • 事件驅動程式設計只和時間依賴有關, 不需要考慮內部的排程
    • 問題更加容易跟蹤: 較慢的按鈕點選反應 和 記憶體資料汙染 時候, 前者問題更加容易定位

12 多執行緒程式設計 VS 事件驅動程式設計

  • 在單個CPU上時間驅動程式比執行緒更加快速
    • 沒有鎖的覆蓋
    • 沒有上下文環境的 切換
  • 事件驅動程式設計更加面向介面程式設計
  • 多執行緒提供了真正的併發性
    • 對於多CPU的機器來說,是可以擴充套件效能
    • 可以長時間的執行處理程式而不需要凍結

13 你需要放棄多執行緒嗎?

  • 不需要的情況: 對於應該程式效能要求很高的服務(比如: 資料庫伺服器)
  • 但是, 儘可能的避免多執行緒程式設計:
    • 對於 GUIs程式, 分散式系統, 效能要求不高的, 使用事件程式設計, 不是多執行緒
    • 只有當真正的多核CPU併發需要使用到的時候,使用多執行緒程式設計
    • 當使用多執行緒程式設計的時候,將多執行緒程式設計模組與其他模組進行隔離, 保持大部分程式碼都是單執行緒模型

隔離多執行緒的模組:
隔離

14 總結

  • 併發從根本上是很難的, 儘可能的避免
  • 多執行緒比事件更加強大,但是這種強大的功能很少真正需要
  • 多執行緒程式設計比事件程式設計更加難以寫出正確的程式碼, 只有真正的專家才能掌握
  • 將事件 程式設計當做基本的開發工具(對於GUIs 和 分散式系統)
  • 只有當效能要求很高的服務時候,才使用 多執行緒