程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 並行思維(三)

並行思維(三)

編輯:關於.NET

線程是什麼玩意

對於並行程序設計來說,線程的重要性不言而喻。

現代操作系統是典型的基於搶占式調度機制的多任務操作系統。

所謂多任務,指同一時刻,允許操作系統內有多個應用程序運行。比如,我們可以在同一時刻,一邊收聽音樂,一邊浏覽網頁。當然,計算機能做到的遠不止於此。

所謂搶占式調度機制,指在操作系統強制讓另外的應用程序運行之前,正在運行的應用程序究竟可以占用 CPU 多少時間。這正是為什麼我們感覺多個應用程序同時運行的真正原因,即使在單核處理器上。舉例來說,Windows 操作系統任務調度的時間間隔大約在 20 毫秒左右(注:這個極短的時間,人眼無法感知,而人眼又欺騙了大腦,我們就感覺有多個程序在同時運行。實踐所知,人眼能感知的最小時間大概是 100 毫秒。因此,Windows Client UI 開發人員應當注意,千萬不要讓你的程序界面響應大於 100 毫秒。假如大於此數,客戶就會感到界面頓卡,帶來槽糕的用戶體驗。Web UI 可以適當放寬界限。但無論怎樣,快速的響應策略應當成為實現良好用戶體驗的首選)。

每個正在運行的應用程序實例都是一個進程。進程通常相對獨立於其他進程運行。尤其是程序所使用的內存資源。舉個例子,浏覽器一般都不能直接訪問音樂播放器的內存資源。如果有這個需求,則需要通過其他手段來達到該目的。比如文件映射,IPC,Socket 等機制。當然,這些機制通常比直接訪問內存資源花費的時間要多。

拿最廣泛應用的 Windows 操作系統而言,從軟件開發人員的角度來看,進程其實就是個計算機資源集合。它是 Windows 操作系統分配和使用系統資源的基本單位。

Windows 進程均包含以下資源:

一個或多個線程。Linux 操作系統早期使用進程來模擬線程。

一個虛擬地址空間,該空間獨立於其他進程地址空間。當然顯式共享的內存除外。請注意文件映射共享物理內存,但共享進程使用不同的虛擬地址來訪問這些映射文件。

一個或多個代碼段,包括 DLL 中的代碼。

一個或多個包含全局變量的數據段。

環境字符串,包含環境變量信息。

進程堆。

其他資源,比如打開的句柄和其他的堆。

而進程的每個線程則共享代碼,全局變量,環境字符串等資源。每個線程都獨立進行調度,是最基本的可執行單元,並包含以下資源:

為過程調用、中斷、異常處理器和自動存儲建立的堆棧。

線程本地存儲(TLS),這是個指針數組,可以讓線程分配存儲以創建其特有的數據環境。

堆棧參數,在創建線程時生成,對每個線程來說通常唯一。

上下文結構,由系統內核通過機器注冊表值來維護。

對於 .NET 開發人員來說,情況又有些不同。CLR 保留了與 Windows 線程分離的權利,而且在某些寄宿情形中,CLR 線程並沒有與 Windows 線程准確匹配。比如,宿主可以告訴 CLR 將每個 CLR 線程表示為一個 Windows 纖程(Fiber)(注:纖程是輕量級的線程。Windows 對纖程一無所知)。

圖 1 展示了一個擁有多線程的 Windows 進程。注意,該圖只是示意圖,沒有展示實際內存地址,也沒有按照比例繪制。

圖 1 Windows 進程示意圖

將問題分解進多個進程,並使用一個嚴格守信的通信機制來協調解決有很多好處。好處之一就是假如某個進程發生錯誤很少能影響到其他進程。由此,操作系統的健壯性有了極大提高,即使犧牲些許效率也可以接受。在多任務操作系統出現之前,單個程序引起整個系統崩潰是相當普遍的事情。

多進程工作適合於那些並不需要頻繁同步的粗粒度並行任務,並且並行任務需要良好的協調。並行的粒度越細,則任務間通信所花費的時間越少。但是如果並行的粒度太細,則任務間通信的數量急劇增多,得不償失。

因此,所有的現代操作系統都支持把多進程的任務細分到多線程來執行。線程跟進程一樣,獨立運行。沒有線程知曉其他線程正在運行什麼,也不知道它們在程序中的位置,除非進行顯式的同步措施。進程和線程的主要區別在於同一進程的所有線程共享該進程的所有資源。很明顯,這讓工作變得更快速。因為線程間上下文切換更快,而且可以在同一地址空間內訪問內存。

線程編程

一旦我們開始使用線程來編程,很多問題便接踵而來。我們應當如何劃分及分配任務以便保持每個可用處理器核心忙碌?我們是否應當在每次擁有新任務時創建一個新線程,還是創建並管理一個線程池?線程數量應當依賴於處理器核心數量嗎?線程處理完任務後,應當再干些什麼?

這些都是實現多任務很重要的問題。但我們並不需要過多擔心,Microsoft 為此做了大量的研究工作。誠如當年的匯編語言開發人員不得不考慮內存分配,內存布局,堆棧指針,寄存器分配等細節,而 C/C++ 則依靠編譯器和庫抽象隔離了這種細節。到了現今的 Java/.NET 開發人員通過 VM 和 GC 已經不再考慮此問題。Microsoft 的 .NET 並行擴展庫正試圖讓開發人員遠離線程管理,站在一個更高的抽象層次上,從而直接利用並行。

但是了解細節仍然必要,它可以讓你編寫的代碼更加高效,以及優化代碼時有的放矢。更詳細可以參考《Programming Applications for Microsoft Windows》、《CLR via C#》、《Patterns for Parallel Programming》等經典著作。

當然樓主以後也會詳細闡述。

線程安全

當編寫的代碼在並行/並發運行時會引發問題,那就不能說是線程安全的。單線程程序僅包含一個控制流,所以也就無所謂線程安全。但在多線程程序中,同一個函數和資源或許被多個控制流並發使用。因此,為多線程編寫的代碼必須要線程安全。

任何變量狀態在被請求寫入時都要確保線程安全。一般來說,我們會使用互斥來確保此刻只有一個線程執行代碼,而把所有其他線程排斥在外。當然還要使用線程安全的庫。如果你要使用某個庫中的函數,請檢查文檔,看是否有線程安全的版本。比如,.NET 的泛型集合類均非線程安全。

如何保證線程安全正是下篇所要闡述的內容。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved