1、定義
線程是操作系統(tǒng)分配CPU時間片的基本單位,每個運行的引用程序為一個進程,這個進程可以包含一個或多個線程。
線程是進程中的執(zhí)行流程,每個線程可以得到一小段程序的執(zhí)行時間,在單核處理器中,由于切換線程速度很快因此感覺像是線程同時允許,其實任意時刻都只有一個線程運行,但是在多核處理器中,可以實現(xiàn)混合時間片和真實的并發(fā)執(zhí)行。但是由于操作系統(tǒng)自己的服務或者其他應用程序執(zhí)行,也不能保證一個進程中的多個線程同時運行。
線程被一個CLR委托給操作系統(tǒng)的進程協(xié)調函數(shù)管理,確保所有線程都可以被分配適當?shù)膱?zhí)行時間,同時保證在等待或阻止的線程不占用執(zhí)行時間。
2、理解
線程與進程的關鍵區(qū)別是:進程是彼此隔離的,進程是操作系統(tǒng)分配資源的基本單位,而同一個進程中的多個線程是共享該進程內(nèi)存堆區(qū)(Heap)的數(shù)據(jù)的,可以進行直接的數(shù)據(jù)共享。但是對于同一進程內(nèi)的不同線程維護各自的內(nèi)存棧(Stack),因此各線程的局部變量是隔離的。通過下面的例子可以看出。
[csharp]view plaincopyPRint?結果輸出的是10個“@”,在兩個線程中都有局部變量i,是彼此隔離的。但是對于共享的引用變量和靜態(tài)數(shù)據(jù),多個線程是會產(chǎn)生不可預知的結果的,這里共享的數(shù)據(jù)也就是“臨界數(shù)據(jù)”,從而引發(fā)了線程安全的概念。
這里輸出的只有一個字符,但是很可能在極少數(shù)情況下會出現(xiàn)輸出兩個字符的情況,而且這是不可預知的。但是,對于共享的引用就不會出現(xiàn)這種情況。
問題:
多線程的問題是使程序中的多個線程的交互變得過于復雜,會帶來較長的開發(fā)時間和間歇性或非重復性的bug。同時線程數(shù)目不能太多,否則頻繁的分配和切換線程會帶來資源和CPU的開銷,一般有一個到兩個工作線程就足夠。
C#中主要使用Thread類進行線程操作,位于System.Threading命名空間下,提供了一系列進行多線程編程的類和接口,有線程同步和數(shù)據(jù)訪問的Mutex、Monitor、Interlocked和AutoResetEvent類,以及ThreadPool類和Timer類等。
首先使用new Thread()創(chuàng)建出新的線程,然后調用Start方法使得線程進入就緒狀態(tài),得到系統(tǒng)資源后就執(zhí)行,在執(zhí)行過程中可能有等待、休眠、死亡和阻塞四種狀態(tài)。正常執(zhí)行結束時間片后返回到就緒狀態(tài)。如果調用Suspend方法會進入等待狀態(tài),調用Sleep或者遇到進程同步使用的鎖機制而休眠等待。具體過程如下圖所示:


Thread類主要用來創(chuàng)建并控制線程,設置線程的狀態(tài)、優(yōu)先級等。創(chuàng)建線程的時候使用ThreadStart委托或者ParameterizedThreadStart委托來執(zhí)行線程所關聯(lián)的部分代碼(也就是工作線程的運行代碼)。
| 屬性 | 說明 |
|---|---|
| CurrentThread | 獲取當前正在運行的線程 |
| IsAlive | 獲取當前線程的執(zhí)行狀態(tài) |
| Name | 獲取或設置線程的名稱 |
| Priority | 獲取或設置線程的優(yōu)先級 |
| ThreadState | 獲取包含當前線程狀態(tài)的值 |
| 方法 | 說明 |
|---|---|
| Abort | 調用此方法的線程引發(fā)ThreadAbortException終止線程 |
| Join | 阻止調用線程,知道某個線程終止時為止 |
| Resume | 繼續(xù)已掛起的線程 |
| Sleep | 將線程阻止指定的毫秒數(shù) |
| Start | 將線程安排被進行執(zhí)行 |
| Suspent | 掛起線程,如果已經(jīng)掛起則不起作用 |
1、創(chuàng)建
使用Thread類的構造函數(shù)創(chuàng)建線程的時候,需要傳遞一個新線程開始執(zhí)行的代碼塊,提供了使用無參數(shù)的TheadStart委托和帶有一個參數(shù)的ParameterizedTheadStart委托。他們的定義如下:
[csharp]view plaincopyprint?任何時候C#使用上述兩個委托中的一個自動進行線程的創(chuàng)建。
[csharp]view%20plaincopyprint?上述方式不傳遞參數(shù),可以使用new%20Thead(Go)的方式直接創(chuàng)建,此時C#會在編譯時自動匹配使用的是ThreadStart委托創(chuàng)建的。下面可以進行傳遞參數(shù)創(chuàng)建線程。
[csharp]view%20plaincopyprint?此時實際在編譯時使用的new%20Thread(new%20ParameterizedThreadStart(Go("hello")))創(chuàng)建的,上述使用Start方法傳遞的參數(shù)會默認采用這種方式構建。
第二種方法是使用Lambda表達式:
[csharp]view%20plaincopyprint?第三種方法是使用匿名方法:
[csharp]view%20plaincopyprint?注意問題:使用Lambda表達式的時候會存在變量捕獲的問題,如果捕獲的變量是共享的,會出現(xiàn)線程不安全的問題。看下面的例子:
[csharp]view%20plaincopyprint?上述由于使用Lambda表達式傳遞參數(shù),在for循環(huán)的作用域內(nèi),新建的十個線程共享了局部變量i,傳遞進入i參數(shù)可能被多個線程已經(jīng)修改,因此每次輸出結果都是不確定的,兩次結果如下:
上述問題,可以使用在循環(huán)體內(nèi)使用一個tmp變量保存每次的變量i值,這樣輸出的就是0到9這十個數(shù)。因為使用tmp變量之后的代碼可以用下面的來理解:
新聞熱點
疑難解答