[.net 物件導向程式設計進階] (16) 多執行緒(Multithreading)(一) 利用多執行緒提高程式效能(上)

yubinfeng發表於2015-07-22

[.net 物件導向程式設計進階] (16) 多執行緒(Multithreading)(一) 利用多執行緒提高程式效能(上)

本節導讀:

隨著硬體和網路的高速發展,為多執行緒(Multithreading)處理並行任務,提供了有利條件。

其實我們每時每刻都在享受多執行緒帶來的便利,多核處理器多執行緒工作、Windows作業系統、Web伺服器都在使用多執行緒工作。

使用多執行緒直接提高了程式的執行效率,因此學習多執行緒對提高程式執行能力非常必要,本節主要介紹多執行緒原理及.NET中多執行緒在.NET物件導向程式設計中的應用。 

讀前必備: 

本節需要了解Lambda表示式基礎及匿名方法和匿名委託相關知識; 

A. 委託                    [.net 物件導向程式設計基礎] (20)  委託 

B. Lamda表示式    [.net 物件導向程式設計進階] (5) Lamda表示式(一)  建立委託  

1. 關於多執行緒

在介紹多執行緒之前,先了解一下程式。

程式:獨立執行的程式稱為程式。(比如Windows系統後臺程式,也可以稱為後臺程式)

執行緒:對於同一個程式分為多個執行流,稱為執行緒。

多執行緒:使用多個執行緒進行多工處理,稱為多執行緒。

2. 如何合理使用多執行緒?

A.對於使用者等待程式處理時,可以使用多執行緒處理耗時任務;

B.對於一些不需要即時完成的任務,可以使用後臺任務執行緒處理;

C.對於多併發任務,可以使用多執行緒同時處理;

D.對於通訊類,比如對執行緒阻塞,可以使用多執行緒。

除過上面的幾個常用的情況,還有很多情況下可以使用多執行緒。

3. 多執行緒的缺點
執行緒自然也有缺點,以下列出了一些:
A.如果有大量的執行緒,會影響效能,因為作業系統需要在他們之間切換;
B.更多的執行緒需要更多的記憶體空間;
C.執行緒會給程式帶來更多的bug,因此要小心使用,比如:執行緒任務在執行完成後,要及時釋放記憶體;
D.執行緒的中止需要考慮其對程式執行的影響。

4. .NET中的兩種多執行緒

.NET本身就是一個多執行緒的的環境。

在.NET中有兩種多執行緒的:

一種是使用Thread類進行執行緒的建立、啟動,終止等操作。

一種是使用ThreadPool類用於管理執行緒池.

5 .NET中使用Thread進行多執行緒處理

5.1 Thread類常用方法

.NET基礎類庫的System.Threading名稱空間提供了大量的類和介面支援多執行緒。System.Threading.Thread類是建立並控制執行緒,設定其優先順序並獲取其狀態最為常用的類。

下面是該類幾個至關重要的方法:
Thread.Start():啟動執行緒的執行;
Thread.Suspend():掛起執行緒,或者如果執行緒已掛起,則不起作用;
Thread.Resume():繼續已掛起的執行緒;
Thread.Interrupt():中止處於Wait或者Sleep或者Join執行緒狀態的執行緒;
Thread.Join():阻塞呼叫執行緒,直到某個執行緒終止時為止
Thread.Sleep():將當前執行緒阻塞指定的毫秒數;
Thread.Abort():以開始終止此執行緒的過程。如果執行緒已經在終止,則不能通過Thread.Start()來啟動執行緒。

下面是一個簡單的執行緒示例: 

先建立一個方法:

//簡單執行緒方法
static void MyThreadStart()
{
    Console.WriteLine("我是一個簡單執行緒");
}

建立執行緒呼叫方法:

//簡單的執行緒
Thread myThread = new Thread(MyThreadStart);
myThread.Start();

執行結果如下:

5.2 Thread類常用屬性

Thread的屬性有很多,我們先看最常用的幾個:

CurrentThread :用於獲取當前執行緒; 

ThreadState 當前執行緒的狀態(5.4介紹);

Name:獲取或設定執行緒名稱;

Priority:獲取或設定執行緒的優先順序(5.5介紹)

ManagedThreadId:獲取當前執行緒的唯一標識

IsBackground:獲取或設定執行緒是前臺執行緒還是後臺執行緒(5.6介紹)

IsThreadPoolThread:獲取當前執行緒是否是託管執行緒池(後面章節會介紹)

下面建立一個執行緒示例,來說明這幾個屬性: 

Thread myThreadTest = new Thread(() =>
{
    Thread.Sleep(1000);
    Thread t = Thread.CurrentThread;
    Console.WriteLine("Name: " + t.Name);
    Console.WriteLine("ManagedThreadId: " + t.ManagedThreadId);
    Console.WriteLine("State: " + t.ThreadState);
    Console.WriteLine("Priority: " + t.Priority);
    Console.WriteLine("IsBackground: " + t.IsBackground);
    Console.WriteLine("IsThreadPoolThread: " + t.IsThreadPoolThread);
})
{
    Name = "執行緒測試",
    Priority = ThreadPriority.Highest
};
myThreadTest.Start();
Console.WriteLine("關聯程式的執行的執行緒數量:"+System.Diagnostics.Process.GetCurrentProcess().Threads.Count);

執行結果如下:

  

5.3 帶引數的執行緒方法 

首先我們使用Lambda表示式來改寫前面“簡單執行緒”中無引數的方法,如下: 

//執行緒一
new Thread(()=>{
    for (int i = 0; i < 5; i++)
        Console.WriteLine("我的執行緒一-[{0}]", i);
}).Start();

 執行結果如下:

上面示例建立的執行緒並沒有帶引數,如果是一個有引數的方法,執行緒該如何建立?

別擔心,.NET為我們提供了一個ParameterizedThreadStart委託來解決帶一個引數的問題,如下:

//執行緒二
new Thread((num) =>{
    for (int i = 0; i < (int)num; i++)
        Console.WriteLine("我的執行緒二--[{0}]", i);
}).Start(5);

執行結果如下:

那麼問題來了,ParameterizedThreadStart委託只有一個包含資料的引數,對於多個引數呢?我們可以使用一個無引數的方法來包裝它,如下:

先建立一個帶引數的方法:

static void myThreadStart(int numA, int numB)
{
    for (int i = (int)numA; i < (int)numB; i++)
        Console.WriteLine("我的執行緒三---[{0}]", i);
}

然後通過無引數的委託來包裝它,如下

//執行緒三
new Thread(() =>myThreadStart(0,5)).Start();

執行結果如下:

 

5.4  Thread狀態

我們對於執行緒啟動以後,如何進行掛起和終止、重新啟用,首先執行緒在執行後有一個狀態。

System.Threading.Thread.ThreadState屬性定義了執行時執行緒的狀態。執行緒從建立到執行緒終止,它一定處於其中某一個狀態。

A.Unstarted:當執行緒被建立時,它處在Unstarted狀態。

B.Running:Thread類的Start() 方法將使執行緒狀態變為Running狀態,執行緒將一直處於這樣的狀態,除非我們呼叫了相應的方法使其掛起、阻塞、銷燬或者自然終止。

C.Suspended:如果執行緒被掛起,它將處於Suspended狀態。

D.Running:我們呼叫resume()方法使其重新執行,這時候執行緒將重新變為Running狀態。一

E.Stopped:旦執行緒被銷燬或者終止,執行緒處於Stopped狀態。處於這個狀態的執行緒將不復存在,正如執行緒開始啟動,執行緒將不可能回到Unstarted狀態。

F.Background:執行緒還有一個Background狀態,它表明執行緒執行在前臺還是後臺。在一個確定的時間,執行緒可能處於多個狀態。

G.WaitSleepJoin、AbortRequested:舉例子來說,一個執行緒被呼叫了Sleep而處於阻塞,而接著另外一個執行緒呼叫Abort方法於這個阻塞的執行緒,這時候執行緒將同時處於WaitSleepJoin和AbortRequested狀態。

H.一旦執行緒響應轉為Sle阻塞或者中止,當銷燬時會丟擲ThreadAbortException異常。

ThreadState列舉的10種執行狀態如下:

 

對於執行緒阻塞和同步問題,將在下一節繼續介紹。

5.5. 執行緒優先順序

對於多執行緒任務,我們可以根據其重要性和執行所需要的資源情況,設定他的優先順序
 System.Threading.ThreadPriority列舉了執行緒的優先順序別,從而決定了執行緒能夠得到多少CPU時間。

高優先順序的執行緒通常會比一般優先順序的執行緒得到更多的CPU時間,如果不止一個高優先順序的執行緒,作業系統將在這些執行緒之間迴圈分配CPU時間。

低優先順序的執行緒得到的CPU時間相對較少,當這裡沒有高優先順序的執行緒,作業系統將挑選下一個低優先順序的執行緒執行。

一旦低優先順序的執行緒在執行時遇到了高優先順序的執行緒,它將讓出CPU給高優先順序的執行緒。

新建立的執行緒優先順序為一般優先順序,我們可以設定執行緒的優先順序別的值,如下面所示: 

對於執行緒的優先順序我們下面做一個實驗,開啟兩個執行緒(一個設定高優先順序,另一個設定低優先順序)。

分別委託兩個方法進行累加,看一下最終結果,程式碼如下:

int numberA = 0, numberB = 0;
bool state = true;
new Thread(() => { while (state)numberA++; }) { Priority = ThreadPriority.Highest, Name="執行緒A" }.Start();
new Thread(() => { while (state)numberB++; }) { Priority = ThreadPriority.Lowest , Name="執行緒B" }.Start();
//讓主執行緒掛件1秒
Thread.Sleep(1000);
state = false;
Console.WriteLine("執行緒A: {0}, 執行緒B: {1}", numberA, numberB);

執行結果如下:

.NET根據優先順序分配了資源,可以看到優先順序較高的執行緒執行的機會較大,但優先順序小的執行緒仍然有較多的機會執行。

5.6  前臺執行緒和後臺執行緒

執行緒有兩種,預設情況下為前臺執行緒,要想設定為後臺執行緒也非常容易,只需要加一個屬性:thread.IsBackground = true;就可以變為一個後臺執行緒了。

重點來了,前後臺執行緒的區別:

A.前臺執行緒:應用程式必須執行完所有的前臺執行緒才能退出;

B.後臺執行緒:應用程式不必考慮其是否全部完成,可以直接退出。應用程式退出時,自動終止後臺執行緒。

下面我們使用一個輸出從0到1000的數字,來實驗一下前臺執行緒和後臺執行緒的區別: 

Thread myThread = new Thread(() =>{for (int i = 0; i < 1000; i++)Console.WriteLine(i);});         

var key = Console.ReadLine();
if (key == "1")
{
    myThread.IsBackground = true;
    myThread.Start();
}
else
{
    myThread.IsBackground = false;
    myThread.Start();
}

當我們在控制檯等級輸入的時候,

如果輸入1(後臺執行緒),執行緒會很快關閉,並不會等輸出完1000個數字再關閉;

如果輸入其它,回車後,則執行緒會等1000個數字輸出完後,視窗關閉;

6. 本節要點:

A.本節主要介紹了執行緒的基本知識;

B.Thread常用的屬性、方法; 

C.Thread委託的方法有多個引數的用法;

D.Thread的優先順序;

E.Thread的執行狀態;

F.前臺執行緒和後臺執行緒;

後面會繼續深入介紹利用執行緒提高程式效能。

==============================================================================================

返回目錄

<如果對你有幫助,記得點一下推薦哦,如有有不明白或錯誤之處,請多交流>

<對本系列文章閱讀有困難的朋友,請先看《.net 物件導向程式設計基礎》>

<轉載宣告:技術需要共享精神,歡迎轉載本部落格中的文章,但請註明版權及URL>

.NET 技術交流群:467189533 .NET 程式設計

==============================================================================================

相關文章