閱讀557 返回首頁    go 阿裏雲 go 技術社區[雲棲]


為什麼多線程是個壞主意

在 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 和 分布式係統)
  • 隻有當性能要求很高的服務時候,才使用 多線程

 轉載自 並發編程網 - ifeve.com

最後更新:2017-05-19 14:32:18

  上一篇:go  職業轉換:從金融工程師到數據科學家
  下一篇:go  CompletableFuture 不能被中斷