當某個應用程序在進行大量運算時候,為了保證應用程序能夠隨時相應客戶的輸入,這個時候我們往往需要讓大量運算和相應用戶輸入這兩個行為在不同的線程中進行。
應用程序經常需要等待一些資源,如等待網絡資源,等待io資源,等待用戶輸入等等。這種情況下使用多線程可以避免CPU長時間處于閑置狀態。
線程內的資源有兩種運行態,即用戶態和內核態。某些運算可以在堆棧上進行,這種情況線程是在用戶態運行的,某些需要高權限運行的指令,或者某些優先級很高的指令需要在操作系統內核中進行,這個時候線程會運行在內核態。出于安全原因,用戶態和內核態的資源是不能夠互相訪問的,因此在用戶態和內核態的切換過程中,我們需要進行相關上下文以及變量的復制,這意味的用戶態和內核態的切換是以一定的時間消耗為代價的。
由于CPU是以時間片為單位進行線程的切換的,由于CPU的運算速度遠大于內存的讀寫速度,因此CPU和內存之間通常有兩級緩存,不同的線程的上下文訪問的數據往往是不同的,這樣線程的切換需要經常頻繁的切換CPU緩存的內容,也需要更新線程的調度信息,這些都是需要花費一定的時間的,因此合理的使用多線程,來避免CPU不停的進行上下文切換。
創建每一個線程的時候,CLR都需要進行一系列的操作,如初始化線程的本地資源,為線程分配用戶模式和內核模式下相應的堆棧,加載相應的托管,非托管資源等。
最簡單常用的創建線程的方式是使用ThreadStart來創建線程,相關代碼如下:
ThreadStart只需要一個委托即可,如果你善于使用匿名方法,也可以用匿名方法來代替委托,使用匿名方法的另一個好處是可以通過匿名方法的閉包特性來為新的線程傳遞參數。
雖然使用匿名方法的閉包特性可以很方便的為線程傳遞參數,但是也往往會帶來一些不容易發現的問題,如下面的程序,由于i變量的共享,在運行的時候輸出會有問題:
正確的寫法應該是這樣的:
如果線程中可能需要捕獲異常,那么我們不能這樣做:
而是這樣做:
System.Threading.Thread幫助我們實現了一些線程的基本操作,如:
屬性名稱 | 說明 |
CurrentContext | 獲取線程正在其中執行的當前上下文。 |
CurrentThread | 獲取當前正在運行的線程。 |
ExecutionContext | 獲取一個 ExecutionContext 對象,該對象包含有關當前線程的各種上下文的信息。 |
IsAlive | 獲取一個值,該值指示當前線程的執行狀態。 |
IsBackground | 獲取或設置一個值,該值指示某個線程是否為后臺線程。 |
IsThreadPoolThread | 獲取一個值,該值指示線程是否屬于托管線程池。 |
ManagedThreadId | 獲取當前托管線程的唯一標識符。 |
Name | 獲取或設置線程的名稱。 |
PRiority | 獲取或設置一個值,該值指示線程的調度優先級。 |
ThreadState | 獲取一個值,該值包含當前線程的狀態。 |
方法名稱 | 說明 |
Abort() | 終止本線程。 |
GetDomain() | 返回當前線程正在其中運行的當前域。 |
GetDomainId() | 返回當前線程正在其中運行的當前域Id。 |
Interrupt() | 中斷處于 WaitSleepJoin 線程狀態的線程。 |
Join() | 已重載。阻塞調用線程,直到某個線程終止時為止。 |
Resume() | 繼續運行已掛起的線程。 |
Start() | 執行本線程。 |
Suspend() | 掛起當前線程,如果當前線程已屬于掛起狀態則此不起作用 |
Sleep() | 把正在運行的線程掛起一段時間。 |
這里我們單獨提一下前臺線程和后臺線程。在CLR中,線程分為前臺線程和后臺線程,當所有前臺的線程執行完之后,CLR會強制結束所有正在運行的后臺線程,并且不會出現任何異常。
因此你應該使用前臺線程來做一些必須完成的任務,比如把流從內存中寫到磁盤上。后臺線程可以做一些不那么重要的事情。一旦線程對象的生命周期開始,你就不能修改IsBackground值。
由于線程是非常昂貴的資源,我們經常需要控制允許多少線程同時運行,如何控制線程的生命周期,如何管理線程,這里我們引入了線程池的概念。
作者:獨上高樓
出處:http://www.companysz.com/myprogram/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。
新聞熱點
疑難解答