多線程學習一(多線程基礎)
阿新 • • 發佈:2019-04-10
rdquo 走了 更多 program 額外 這一 正在 runnable 創建 前言
多線程、單線程、進程、任務、線程池...等等一些術語到底是什麽意思呢?到底什麽是多線程?它到底怎麽用?我們一起來學習一下多線程的處理
如何理解
進程:進程是給定程序當前正在執行的實例(操作系統的一個基本功能就是管理進程)
線程:線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位
單線程程序是僅包含一個線程的進程。多線程程序的進程則包含兩個或更多的線程
線程安全:在多線程程序中運行時具有正確的表現,就說代碼是線程安全的
任務:任務是可能有高延遲的工作單元,目的是生成一個結果值,或者產生想要的效果
線程池:線程池是多個線程的集合,也是決定如何向線程分配工作的邏輯
多線程處理的目的和方式
多線程處理主要用於兩個方面:
1、實現多任務
2、解決延遲
其中主要還是解決延遲問題:例如導入一個大文件的時候需要較長的時間,為了允許用戶隨時點擊取消,開發者創建一個額外的線程來執行導入,這樣就可以隨時點擊取消,而不是直接凍結UI直至導入完成。
當然,如果有足夠的內核使得每一個線程都能分配到一個內核的話,那麽每個線程就都使用自己各自的CPU。但是如今雖然有了多核機器,但是線程數任然大於內核的數量。
為了解決這一粥(CPU內核)少僧(線程)多的矛盾,操作系統通過稱為時間分片的機制來模擬多個線程並發運行。操作系統以極快的速度從一個線程切換到另一個線程,給人的感覺就是所有的線程都在同時執行
時間片:處理器在切換到下一個線程之前,執行一個特定的線程的時間周期稱之為時間片或量子
上下文切換:在一個給定的內核中改換執行線程的動作稱為上下文切換
不管是真正的多核並行運行還是使用時間分片的機制來模擬,我們說“一起”進行的兩個操作是並發的。並行編程是指將一個問題分解成較小的部分,並異步的發起對每個部分的處理,使它們能並發地得到處理。
其中我們也需要考慮的是性能問題,不要產生一種誤導就是多線程的代碼會更快,多線程知識解決處理器受限的問題。同時我們需要註意性能問題
多線程處理遇到的問題
寫一個多線程程序既復雜又困難,因為在單線程程序中許多成立的假設在多線程中變得不成立了,其中包括原子性、競態條件、復雜的內存模型以及死鎖
1、大多數操作不是原子性的
什麽是競態條件
官方的定義是如果程序運行順序的改變會影響最終結果,這就是一個競態條件(race condition).
int Balance=10; int Money=6; if(Balance>Money) { Balance-=Money; }在這段代碼中,如果出現兩個線程都拿到Balance(當前余額)並且都進入了if中,第一個拿走了Money(取走的金額),然後第二個沒有經過驗證繼續執行了Balance-=Money的操作,最後得出的結果是Balance剩下-2。這就導致了出現錯誤。 2、競態條件造成的不確定性
Runnable r1 = () -> { // do something }; Runnable1 r2 = () -> { // do another thing }; Thread producer = new Thread(new ThreadStart(Runnable)); Thread producer1 = new Thread(new ThreadStart(Runnable1)); producer.Start(); producer1.Start();
兩個線程同時把一個類的靜態成員做50詞自增加1的操作,即
SomeClass.someMember++;
寫在兩個線程中,都運行50次,運行結束以後用主線程去取這個變量的值幾乎不可能是100. 有的時候是97,有的時候是98,這是用來說明競態條件的最有效例子。 3、內存模型的復雜性 假設兩個線程在兩個不同的進程中運行,但要訪問同一個對象中的字段,目前的處理器不會每次都去訪問主內存,相反訪問的是處理的“高速緩存”中生成的一個本地副本,這個緩存會定時的與主內存同步,這就意味著這兩個不同進程中的線程以為自己讀取到的是相同的位置,實際讀取到的不是那個字段實時更新的,造成兩個線程獲取的字段結果不一致。 4、鎖定造成死鎖 當然肯定有辦法解決非原子性,防止競態條件,並且確保處理器的高速緩存在必要時進行同步的。解決這些問題的主要機制是lock語句,這個語句就是將一部分代碼設置為“關鍵”代碼,一次只有一個線程能執行它,如果多個線程需要訪問它,操作系統只允許進入一個,其他的將被掛起。 當然鎖也有問題,加入不同的線程以不同的順序獲取鎖,就可能造成死鎖,這樣的結果就是你等著我釋放鎖,我等著你釋放鎖。此時只有對方釋放了鎖之後才能繼續運行,線程阻塞,造成了這段代碼的徹底死鎖 既然鎖可以解決前三個問題,但是可能會出現死鎖的問題。那麽我們改如何避免或解決死鎖的問題呢? 如何避免死鎖 既然加入不同的線程以不同的順序獲取鎖可能造成死鎖,那麽我們只有確保所有的線程都是按照相同的順序獲得鎖,那麽死鎖就不會發生。
class Program { private static object objA = new object(); private static object objB = new object(); static void Main() { Program a = new Program(); Thread th = new Thread(new ThreadStart(a.Lock1)); th.Start(); lock (objB) { Console.WriteLine("我是objB,想獲取objA"); lock (objA) { Console.WriteLine("死鎖了"); } } Console.WriteLine("死鎖了"); Console.WriteLine(); } public void Lock1() { lock (objA) { Thread.Sleep(500); Console.WriteLine("我是objA,想獲取objB"); lock (objB) { Console.WriteLine("死鎖了"); } } } }
上面是獲取鎖的順序不恰當而導致死鎖的例子。如果把其中一個獲取鎖的位置改變一下就不會造成死鎖了,例如在Lock1中先獲取objB再獲取objA的話就不會造成死鎖了。所有我們平時在使用lock時一定得確保鎖的順序,不然很容易造成死鎖的。
本文參考c#本質論6.0多線程處理
多線程學習一(多線程基礎)