一、概述與概念
c#支援通過多執行緒並行地執行**,乙個執行緒有它獨立的執行路徑,能夠與其它的執行緒同時地執行。乙個c#程式開始於乙個單執行緒,這個單執行緒(也稱為「主線程」)是被clr和作業系統自動建立的,能夠通過新增額外的執行緒建立多執行緒。
下面是個簡單的例子:
class program01static void writey()
}
clr分配每個執行緒到它自己的記憶體堆疊上,來保證區域性變數的分離執行。
class program02static void go()
}
其中我們定義乙個區域性變數,然後主線程和新建立的執行緒上同時呼叫這個方法。變數cycles的副本分別在各自記憶體堆疊中建立,輸出也是一樣的。
當執行緒引用了一些公用的目標例項時,他們會共享資料:下面是例項:
1class
program03211
//注意go現在是乙個例項方法
12void
go()
1318
}19 }
因為在相同的例項中,兩個執行緒都呼叫了go(),它們共享了done欄位,所以輸出乙個「done」,而不是兩個。
classprogram04
static
void
go()
}}
上述的兩個例子輸出實際上是不確定的:它可能列印兩次的「done」,如果我們在go方法裡面調換**順序,這種不確定性更明顯。因為乙個執行緒在判斷if塊的時候,正好另乙個執行緒正在執行輸出語句。這就引出了另乙個關鍵的概念——執行緒安全。
當讀寫公共欄位的時候,需要我們我們提供乙個排他鎖;
class program05static void go()}}
}
當兩個執行緒爭奪乙個鎖的時候,乙個執行緒等待,或者被阻止到那個鎖變的可用。在這中情況下,就確保了在同一時刻只有乙個執行緒能進入臨界區。**以如此方式在不確定的多執行緒環境中被叫做執行緒安全。
二、執行緒是如何工作的
執行緒被乙個執行緒協調程式管理著——乙個clr委託給作業系統的函式。執行緒協調程式確保分配適當的時間給所有活動的執行緒;其中那些等待或被阻止的執行緒都是不消耗cpu時間的。
在單核處理器的電腦中,執行緒協調程式完成乙個時間片之後,迅速地在活動執行緒之間進行切換執行。通常時間片在10毫秒內,要比處理執行緒切換的消耗(通常在幾微妙區間)大的多。
在多核的電腦中,多執行緒被實現成混合時間片和真實的併發,不同的執行緒在不同的cpu上執行。
執行緒由於外部因素(比如時間片)被中斷被成為被搶占,乙個執行緒在被搶占的那一時刻就失去了對它的控制權。
三、執行緒vs程序
屬於乙個單一的應用程式的所有的執行緒邏輯被包含在乙個程序中,程序指乙個應用程式多執行的作業系統單元。
執行緒與程序有某些相似的地方;比如說進行通常以時間片方式與其它在電腦中執行的程序的方式與乙個c#程式執行緒執行的方式是大致相同。兩者的關鍵區別在於程序彼此是完全隔絕的,執行緒與執行在相同程式中的其它執行緒共享記憶體。乙個執行緒可以在後台讀取資料,而另乙個執行緒可以在前台展現已讀取的資料。
四、多執行緒的使用場景
何時使用多執行緒
多執行緒程式一般用來在後台執行耗時的任務。主線程保持執行,並且工作執行緒做它的後台工作。在沒有使用者介面的程式裡,當乙個任務有潛在的耗時(因為它在等待另一台電腦的響應,比如伺服器)的實現,多執行緒就特別有意義。用工作執行緒完成任務意味著主線程可以立即做其它的事情。
另乙個多執行緒的用途是在方法中完成乙個複雜的計算工作:這個方法會在多核的電腦上執行的更快(因為工作量被多個執行緒分開,使用environment.processorcount屬性來偵測處理晶元的數量)。
乙個c#程式可以通過明確的建立和執行多執行緒,也可以使用.net framework的暗中使用了多執行緒的特性——比如backgroundworker類、執行緒池、threading timer、遠端伺服器或web services 或asp.net 程式。
何時不要使用多執行緒
多執行緒的互動複雜、開發周期長,以及帶來間歇行和非重複性的bugs。頻繁的分配和切換執行緒時,多執行緒會增加資源和cpu的開銷。在某些情況下,太多的i/o操作是非常棘手的,乙個或兩個工作執行緒要比有眾多的執行緒在相同時間執行任務快的多。
五、建立和開始使用多執行緒
執行緒用thread類來建立,通過threadstart委託來指明方法先哦那個**開始執行。
threadstart定義如下:
public delegate void threadstart();
呼叫start方法後,執行緒開始執行,執行緒一直到它所呼叫的方法返回後結束。
下面的示例通過threadstart委託建立多執行緒:
classthreadtest
static
void go()
乙個執行緒可以通過c#堆委託簡短的語法更便利地建立出來:
staticvoid
main()
static
void go()
在這種情況,threadstart被編譯器自動推斷出來,另乙個快捷的方式是使用匿名方法來啟動執行緒:
static void main() );t.start();
}
執行緒有乙個isalive屬性,在呼叫start()之後直到執行緒結束之前一直為true。
乙個執行緒一旦結束便不能重新開始了。
將資料傳入threadstart中
我們想更好地區分開每個執行緒的輸出結果,讓其中乙個執行緒輸出大寫字母。我們傳入乙個狀態 字到go中來完成整個任務,但我們不能使用threadstart委託,因為它不接受引數,所幸的是,.net framework定義了另一 個版本的委託叫做parameterizedthreadstart, 它可以接收乙個單獨的object型別引數:
public delegate void parameterizedthreadstart (object obj);
之前的例子看起來是這樣的:
class threadteststatic void go (object uppercase)
在整個例子中,編譯器自動推斷出parameterizedthreadstart委託,因為go方法接收乙個單獨的object引數,就像這樣 寫:
thread t = new thread (new parameterizedthreadstart (go));
t.start (true)
六、名稱執行緒
執行緒可以統統它的name屬性進行命名,這非常利於除錯。執行緒的名字可以被任何時間設定,但是只能設定一次,重新命名會引發異常。
七、前台執行緒和後台執行緒
執行緒預設為前台執行緒,這意味著任何前台執行緒在執行都會保持程式存活。c#也支援後台執行緒,它們不維持程式存活。可以通過isbackgound屬性控制它的前後臺狀態,改變執行緒從前台到後台不會以任何方式改變它在cpu協調程式中的優先順序和狀態。
八、執行緒優先順序
執行緒的priority屬性確定了執行緒相對於其它同一程序的活動的執行緒擁有多少執行時間,以下是級別:
enum threadpriority
只有多個執行緒同時為活動時,優先順序才有作用。設定乙個執行緒的優先順序為高一些,並不意味著它能執行實時的工作,因為它受限於程式的程序的級別。要執行實時的工作,必 須提公升在system.diagnostics 命名空間下process的級別,像下面這樣
process.getcurrentprocess().priorityclass = processpriorityclass.high;
high大體上被認為最高的有用程序級別。
如果乙個實時的程式有乙個使用者介面,提公升程序的級別是不太好的,因為當使用者介面ui過於複雜的時候,介面的更新耗費過多 的cpu時間,拖慢了整台電腦。最理想的方案是使實時工作和使用者介面在不同的程序(擁有不同的優先順序)執行,通 過remoting或共享記憶體方式進行通訊,共享記憶體需要win32 api中的 p/invoking。
九、異常處理
任何建立執行緒範圍內的try/catch/finally塊,當執行緒開始執行便不再與其有任何關係。
考慮下面的程式:
public static void main()catch (exception ex)
}static void go()
public static void main()static void go()
catch (exception ex)
多執行緒入門
跟前幾篇的風格一樣,我會在開篇的時候舉乙個現實生活中的例子,通過這個例子來對映一些晦澀枯燥的計算機程式設計專業知識,在讓讀者朋友很好地理解理論概念的同時,又避免了閱讀教科書時的枯燥感覺。這次我要舉的例子是公司。不一定是it公司,盡量和程式設計領域遠一點兒吧,那就假設是一家搬家公司吧。假如我們把公司看...
C C 多執行緒入門
在學習多執行緒程式設計之前,必須先知道什麼是 執行緒函式,執行緒函式就是另乙個執行緒的入口函式.預設情況下乙個我們所寫的 都是只有乙個執行緒的,而這個執行緒的入口函式就是main 函式,這是系統預設的.而我們建立的另乙個執行緒也需要乙個函式來進入,這個函式就叫做執行緒函式.在c c 中,可以呼叫 執...
java 多執行緒入門
繼承thread類 實現runnable介面 優先選擇 public class testthread1 class runner1 implements runnable public class testthread2 class runner1 implements runnable publ...