多線程在構建大型系統的時候是需要重點關注的一個重要方面,特別是在效率(系統跑得多快?)和效能(系統工作正常?)之間做一個權衡的時候。恰當的使用多線程可以極大的提高系統效能。
什麼是線程?
每個正在系統上啟動並執行程式都是一個進程。每個進程包含一到多個線程。進程也可能是整個程式或者是部分程式的動態執行。線程是一組指令的集合,或者是程式的特殊段,它可以在程式裡獨立執行。也可以把它理解為代碼啟動並執行上下文。所以線程基本上是輕量級的進程,它負責在單個程式裡執行多任務。通常由作業系統負責多個線程的調度和執行。
什麼是多線程?
多線程是為了使得多個線程並行的工作以完成多項任務,以提高系統的效率。線程是在同一時間需要完成多項任務的時候被實現的。
使用線程的好處有以下幾點:
·使用線程可以把佔據長時間的程式中的任務放到後台去處理
·使用者介面可以更加吸引人,這樣比如使用者點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
·程式的運行速度可能加快
·在一些等待的任務實現上如使用者輸入、檔案讀寫和網路收發資料等,線程就比較有用了。在這種情況下我們可以釋放一些珍貴的資源如記憶體佔用等等。
還有其他很多使用多線程的好處,這裡就不一一說明了。
一些執行緒模式的背景
我們可以重點討論一下在win32環境中常用的一些模型。
·單執行緒模式
在這種執行緒模式中,一個進程中只能有一個線程,剩下的進程必須等待當前的線程執行完。這種模型的缺點在於系統完成一個很小的任務都必須佔用很長的時間。
·塊執行緒模式(單線程多塊模型sta)
這種模型裡,一個程式裡可能會包含多個執行的線程。在這裡,每個線程被分為進程裡一個單獨的塊。每個進程可以含有多個塊,可以共用多個塊中的資料。程式規定了每個塊中線程的執行時間。所有的請求通過windows訊息佇列進行序列化,這樣保證了每個時刻只能訪問一個塊,因而只有一個單獨的進程可以在某一個時刻得到執行。這種模型比單執行緒模式的好處在於,可以響應同一時刻的多個使用者請求的任務而不只是單個使用者請求。但它的效能還不是很好,因為它使用了序列化的執行緒模式,任務是一個接一個得到執行的。
·多線程塊模型(自由線程塊模型)
多線程塊模型( mta )在每個進程裡只有一個塊而不是多個塊。這單個塊控制著多個線程而不是單個線程。這裡不需要訊息佇列,因為所有的線程都是相同的塊的一個部分,並且可以共用。這樣的程式比單執行緒模式和sta的執行速度都要塊,因為降低了系統的負載,因而可以最佳化來減少系統idle的時間。這些應用程式一般比較複雜,因為程式員必須提供線程同步以保證線程不會並發的請求相同的資源,因而導致競爭情況的發生。這裡有必要提供一個鎖機制。但是這樣也許會導致系統死結的發生。
多線程在.net裡如何工作?
在本質上和結構來說,.net是一個多線程的環境。有兩種主要的多線程方法是.net所提倡的:使用threadstart來開始你自己的進程,直接的(使用threadpool.queueuserworkitem)或者間接的(比如stream.beginread,或者調用begininvoke)使用threadpool類。一般來說,你可以"手動"為長時間啟動並執行任務建立一個新的線程,另外對於短時間啟動並執行任務尤其是經常需要開始的那些,進程池是一個非常好的選擇。進程池可以同時運行多個任務,還可以使用架構類。對於資源緊缺需要進行同步的情況來說,它可以限制某一時刻只允許一個線程訪問資源。這種情況可以視為給線程實現了鎖機制。線程的基類是system.threading。所有線程通過cli來進行管理。
·建立線程:
建立一個新的thread對象的執行個體。thread的建構函式接受一個參數:
thread dummythread = new thread( new threadstart(dummyfunction) );
·執行線程:
使用threading命名空間裡的start方法來運行線程:
dummythread.start ();
·組合線程:
經常會出現需要組合多個線程的情況,就是當某個線程需要其他線程的結束來完成自己的任務。假設dummythread必須等待dummyprioritythread來完成自己的任務,我們只需要這樣做:
dummyprioritythread.join() ;
·暫停線程:
使得線程暫停給定的秒
dummyprioritythread. sleep (<time in second>);
·中止線程:
如果需要中止線程可以使用如下的代碼:
dummyprioritythread.abort();
·同步
經常我們會遇到需要線上程間進行同步的情況,下面的代碼給出了一些方法:
using system;using system.threading;namespace synchronizationthreadsexample{
class synchronizationthreadsexample{
private int counter = 0;
static void main( ) {
synchronizationthreadsexample ste = new synchronizationthreadsexample();
ste.threadfunction( );
}
public void threadfunction ( ) {
thread dummythread = new thread( new threadstart(somefunction) ;
dummythread.isbackground=true;
dummythread.name = "first thread";
dummythread.start( );
console.writeline("started thread {0}", dummythread.name);
thread dummyprioritythread = new thread( new threadstart(somefunction) );
dummyprioritythread.isbackground=true;
dummyprioritythread.name = "second thread";
dummyprioritythread.start( );
console.writeline("started thread {0}", dummyprioritythread.name);
dummythread.join( );
dummyprioritythread.join( );
}
public void somefunction( ) {
try {
while (counter < 10) {
int tempcounter = counter;
tempcounter ++;
thread.sleep(1);
counter = tempcounter;
console.writeline( "thread {0}. somefunction: {1}",thread.currentthread.name, counter);
}
}
catch (threadinterruptedexception ex) {
console.writeline( "exception in thread {0} ", thread.currentthread.name);
}
finally {
console.writeline( "thread {0} exiting. ",thread.currentthread.name);
}
}
}
}
·使用interlock
c#提供了一個特殊的類叫做interlocked,就是提供了鎖機制的實現,我們可以加入如下的代碼實現鎖機制:
interlocked.somefunction (ref counter);
·使用鎖
這是為了鎖定代碼關鍵地區以進行同步,鎖定代碼如下:
lock (this){ some statements ;}
·使用 monitor
當有需要進行線程管理的時候我們可以使用:
monitor.enter(this);
其他也有一些方法進行管理,這裡就不一一提及了。
線程的缺點
線程自然也有缺點,以下列出了一些:
·如果有大量的線程,會影響效能,因為作業系統需要在他們之間切換;
·更多的線程需要更多的記憶體空間
·線程會給程式帶來更多的bug,因此要小心使用
·線程的中止需要考慮其對程式啟動並執行影響
·通常塊模型資料是在多個線程間共用的,需要一個合適的鎖系統替換掉資料共用