並發和線程是相互糾纏的問題,選擇先學哪個也許很難。本文將先講講並發,它將為後面學習線程准備一些該先了解一下的知識。
術語
並發
並發是這樣一個狀態——許多Task同時啟動。當並發被實現得恰恰當當時,它可能被認為是“harmony”。而實現得糟糕時,就成了”chaos“。
在大部分情況中,所說的Task指的都是線程。然而,Task也可以是進程或者纖程。
兩者之間的分界通常很清楚,而使用合適的技術才是關鍵
Contention
確切的說何為Contention?Contention就是當多於一個Task嘗試著同時訪問那獨獨一個資源時的情況。
如果你是在大家庭長大的孩子,可能這個比喻能很好的解釋它意思。想想家裡要是有六個小孩,媽媽把一塊小匹薩放在桌上作為晚餐,會發生什麼樣的情況。那就是Contention的含義。
無論何時,只要多個並發的Task需要用讀/寫的方式訪問數據,對數據的訪問都必須得到控制從而保護它的完整性。如果訪問沒有得到控制,兩個或者更多的Task可能會“崩潰”。當其中的一個嘗試著要讀取變量時,另外一個可能要同時對它進行寫入。如果一個Task正在寫,而另外一個正在讀,那個讀的Task可能讀取了部分寫入的數據從而獲得的是損壞了的數據。一般這樣的操作是不會立即導致異常的,而只會在這之後給程序帶來錯誤。
Contention問題經常是在低流量的Implementation中不會出現,因而在開發階段經常是一點問題都沒有。所以在開發的階段應該采用合適的技術和壓力測試。否則就有一些像玩Russian Roulette,問題在開發階段僅僅是偶爾出現但是在部署階段變成了頻繁出現。
資源保護
資源保護是用來阻止由Contention帶來的問題的解決辦法。資源保護的目的是一次僅讓一個Task訪問指定的資源。
解決Contention
無論何時,只要多個線程需要以讀/寫的方式訪問數據,對數據的訪問都必須得到控制從而保護它的完整性。這可能對於不熟悉線程操作的程序員來說intimidating。然而,大部分服務器不需要全局數據。這些程序一般在啟動過程中初始化之後只需要讀取數據。只要沒有寫操作,線程可以沒有任何副作用的讀
取全局數據。
下面講的是解決Contention最常用的辦法。
只讀
最簡單的辦法是只讀。任何簡單類型(整數,字符串,內存)以只讀的方式訪問不需要任何保護。這也可以擴展到諸如TLists等許多復雜類型。只要它們不以讀/寫方式訪問任何全局或者成員變量,類型在只讀方式時都是安全的。
此外,資源可以在任何可能的讀操作之前被改寫。這允許了在讀取它的Task啟動之前,先初始化資源。
Atomic操作
有一種方法是說如果操作是Atomic,資源不要被保護。Atomic操作是這樣一種操作,它太小了以至於不能被計算機處理器分劃開來。因為它尺寸小,從而它不會受到Contentiion的影響因為它將由自身執行而且在執行過程中不會有Task的切換。一般情況下,Atomic操作是被編譯為一條匯編指令的源代碼。
典型的任務諸如讀取或者寫入一個整型或者布爾型變量被認為是Atomic操作,因為它們被編譯為一條Move指令。然而我推薦你絕對不要依賴原子操作,因為某些情況下甚至寫入一個整型或者布爾型變量都能包括多於一個的動作,這要看數據首先是從哪兒讀來的。此外,這還依賴於編譯器內部的奧秘,而這可能會在不告知你的情況下做出改變。依賴源代碼級的Atomic操作將產生未來會有問題的代碼而且可能在多處理器的機器或者別的操作系統上行為非常不同。
我曾經見過一個鐵打不動的Atomic操作。然而一個非常prominent的未來事件證明了我的觀點,那就是.Net。你的代碼首先編譯為IL,然而再編譯為機器碼,可能還是在不同廠商的不同平台上,你還能確信你的代碼最終還是Atomic操作嗎?
選擇最終還是要看你自己,當然有許多聲音圍繞著對Atomic操作的偏愛和反對。在大部分情況下,依賴原子操作僅僅節省了幾毫秒,以及幾字節的代碼。我強烈推薦不要去使用Atomic操作,因為它們帶來的好處如此至少而liabilitIEs如此之巨大。把所有操作都當作非Atomic操作來對待。
操作系統的支持
許多操作系統對非常基本的線程安全的操作提供了支持。
Windows支持一套稱為Interlocked Function的函數。這些函數的用處非常有限,而且僅僅包括簡單的對整數的操作,諸如步增,步減,加,Swap以及Swap-Compare。
函數的數目和Windows的版本有關,而且可能在低版本的
[1] [2] [3] [4] 下一頁