什麼是多線程?
最開始,線程只是用於分配單個處理器的處理時間的一種工具。但假如操作系統本身支持多個處理器,那麼每個線程都可分配給一個不同的處理器,真正進入"並行運算"狀態。
從程序設計語言的 角度看,多線程操作最有價值的特性之一就是程序員不必關心到底使用了多少個處理器。程序在邏輯意義上被分割爲數個線程;假如機器本身安裝了多個處理器,那 麼程序會運行得更快,毋需作出任何特殊的調校。根據前面的論述,大家可能感覺線程處理非常簡單。但必須注意一個問題:共享資源!如果有多個線程同時運行, 而且它們試圖訪問相同的資源,就會遇到一個問題。舉個例子來說,兩個線程不能將信息同時發送給一臺打印機。爲解決這個問題,對那些可共享的資源來說(比如打印機),它們在使用期間必須進入鎖定狀態。所以一個線程可將資源鎖定,在完成了它的任務後,再解開(釋放)這個鎖,使其他線程可以接着使用同樣的資源。
上面的對於線程間的訪問,我們會在後面解釋清楚,這裏我摘要的概念,只是爲了新學習的同學們有一個線程的大概概念,當然我們既然知道對於單核的計算機來說,CPU劃分時間片給應用去執行程序,那麼此時的線程一定是同步的嗎,肯定不是同時執行的,只是CUP劃分給不同線程間的時間片很短,給人感覺是同時執行的而已,當然,現在多核的電腦普及了,處理簡單的線程是可以達到真正同步的效果。好了就不閒扯這些基本概念了,但是我們要知道進程,應用程序域,運行時宿主程序,線程。
這裏就簡要的提一下,應用程序域是CLR爲應用程序提供隔離單元,一個進程可以有多個應用程序域,每一個應用程序域都可以加載一個應用程序,應用程序域通常由運行時宿主(例如:IE,window外殼程序,ASP.NET)創建,每個應用程序域都是由單個單個線程啓動的,但是該應用程序域中的代碼可以創建附加應用應用程序域和附加線程,之後就我們提到了多線程啦。。。。。。。
Thread類的一些常用方法
提到線程就必須提到Thread類,爲了下面的演示我們在這裏列舉幾個常見的方法和屬性:
構造函數這個可是必看的,所以咋們先看這個:
1public Thread( ParameterizedThreadStart start)
2public Thread(ThreadStart start)
3public Thread( ParameterizedThreadStart start, int maxStackSize)
4public Thread( ThreadStart start, int maxStackSize)
然後當我們繼續查看其參數的時候我們發現了
1 [ComVisibleAttribute(true)]
2publicdelegatevoid ThreadStart()
3 [ComVisibleAttribute(false)]
4publicdelegatevoid ParameterizedThreadStart( Object obj)
看到我們知道了線程構造函數需要的是兩個無返回類型的委託,具體怎麼用我們先介紹完幾個常用的方法就開始測試.
Start () 導致操作系統將當前實例的狀態更改爲 ThreadState .Running 。
Join () 在繼續執行標準的 COM 和 SendMessage 消息泵處理期間,阻塞調用線程,直到某個線程終止爲止。
Sleep(Int32) 將當前線程掛起指定的時間。
VolatileRead(Byte)讀取字段值無論處理器的數目或處理器緩存的狀態如何,該值都是由計算機的任何處理器寫入的最新值。
VolatileWrite(Byte , Byte) 立即向字段寫入一個值,以使該值對計算機中的所有處理器都可見。
Interrupt 中斷處於 WaitSleepJoin 線程狀態的線程。
現在開始我們的主要工作
1.無參數的構造函數
我們先來創建兩個線程,來執行兩個無參數構造的函數,這裏提一下,就是上面所說的線程的構造函數可以簡單地記成有參和無參的構造函數,這裏我們使用無參的構造函數public Thread(ThreadStart start),我們來看下效果,首先提示下:現在的系統基本都是支持搶先多任務處理的,我們這裏就直接上代碼:
1using System;
2using System.Threading;
4namespace 多線程
5 {
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Test t = new Test();
11 Thread h1 = new Thread(t.InstanceMethod);
12 Thread h2 = new Thread(new ThreadStart(Test.staticMethod));
13 h1.Start();
14 h2.Start();
15
16 Console.ReadKey();
17 }
18 }
19class Test
20 {
21publicvoid InstanceMethod()
22 {
23
24for (int i = 0; i < 8; i++)
25 {
26 Console.WriteLine(i+"========線程1========");27 Thread.Sleep(1000);28 }
29 }
30publicstaticvoid staticMethod()
31 {
32for (int i = 0; i < 8; i++)
33 {
34 Console.WriteLine(i + "========線程2========");35 Thread.Sleep(1000);
36 }
37 }
38 }
39 }
從你和我遲鈍的感官我們可以看出,這兩個線程貌似是同時執行的,但是如果我們不使用Sleep()短暫的掛起線程,我們甚至連眨眼都來不及線程就結束了,難道他們真是同時執行的嗎(我的是四核的CPU),難道他們是分配到不同的CPU上執行了,多說廢話無用,我們自己來測試,我想了個最垃圾的方法,就是記錄線程執行時的毫秒數,修改代碼如下:
1using System;
2using System.Threading;
3
4namespace 多線程
5 {
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Test t = new Test();
11 Thread h1 = new Thread(t.InstanceMethod);
12 Thread h2 = new Thread(new ThreadStart(Test.staticMethod));
13 h1.Start();
14 h2.Start();
15 Console.ReadKey();
16 }
17 }
18class Test
19 {
20publicvoid InstanceMethod()
21 {
22
23for (int i = 0; i < 8; i++)
24 {
25 Console.WriteLine(i+"========線程1:當前所處毫秒數:{0}========",DateTime.UtcNow.Millisecond);
26 Thread.Sleep(1000);
27 }
28 }
29publicstaticvoid staticMethod()
30 {
31for (int i = 0; i < 8; i++)
32 {
Console.WriteLine(i + "========線程2:當前所處毫秒數:{0}========", DateTime.UtcNow.Millisecond);
34 Thread.Sleep(1000);
35 }
36 }
37 }
38 }
執行效果如下:
我們發現,原來這都是有操作系統決定的,什麼那個線程先啓動,人家操作系統纔是東道主,人家自己決定,於是我們學到了一個道理,人在屋檐下,怎能不低頭!
2.有參數的構造函數
現在我們使用有參數的構造函數來創建線程 public Thread( ParameterizedThreadStart start),這些都很簡單我們就直接上代碼:
1using System;
2using System.Threading;
3
4namespace 多線程
5 {
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Test t = new Test();
11 Thread h1 = new Thread(new ParameterizedThreadStart(t.InstanceMethod));
12 Thread h2 = new Thread(new ParameterizedThreadStart(Test.staticMethod));
13 h1.Start(8);
14 h2.Start("執行靜態方法");
15 Console.ReadKey();
16 }
17 }
18class Test
19 {
20publicvoid InstanceMethod(object times)
21 {
22
23for (int i = 0; i < (int)times; i++)
24 {
25 Console.WriteLine("{0}========執行實例方法========", i);
26 Thread.Sleep(1000);
27 }
28 }
29publicstaticvoid staticMethod(object str)
30 {
31for (int i = 0; i < 8; i++)
32 {
33 Console.WriteLine("{0}========{1}========", i, str);
34 Thread.Sleep(1000);
35 }
36 }
37 }
38 }
我們注意下ParameterizedThreadStart的參數,是一個Object類型的參數,因此我們在委託調用的函數內部進行了轉換。
運行效果和上面都是一樣的,但是既然提到了ParameterizedThreadStart的參數,是一個Object類型的參數,也就意味着我們什麼類型都可以傳進去,這樣就既不安全,我們怎麼改造呢,這裏就引入了我們的下一個話題。
3.創建實現2中的效果一個可靠的委託方法
方法就是我們將所需的執行函數,以及參數都獨立的封裝到一個單獨的類中。在類的內部通過該類的構造函數給需要執行的函數傳遞所需要的值,這樣我們就可以照樣使用public Thread( ParameterizedThreadStart start)。
現在我們看代碼:
1using System;
2using System.Threading;
3
4namespace 多線程
5 {
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Temp t = new Temp(8, "執行靜態方法");
11 Thread h1 = new Thread(new ThreadStart(t.InstanceMethod));
12 Thread h2 = new Thread(new ThreadStart(Temp.staticMethod));
13 h1.Start();
14 h2.Start();
15 Console.ReadKey();
16 }
17 }
18///<summary>
19/// 封裝了線程所需的數據和執行的方法
20///</summary>
21class Temp
22 {
23privateint times;
24privatestaticstring str;
25public Temp(int times, string content)
26 {
27this.times = times;
28 str = content;
29 }
30publicvoid InstanceMethod()
31 {
32int lineCount = 0;
33for (int i = 0; i < times; i++)
34 {
35 Console.WriteLine("{0}========執行實例方法========", i);
36 lineCount++;
37 Thread.Sleep(50);
38 }
39 Console.WriteLine("實例方法輸出:{0}行數據",lineCount);
40 }
41publicstaticvoid staticMethod()
42 {
43int lineCount = 0;
44for (int i = 0; i < 8; i++)
45 {
46 Console.WriteLine("{0}========{1}========", i, str);
47 Thread.Sleep(50);
48 lineCount++;
49 }
50 Console.WriteLine("靜態方法輸出:{0}行數據", lineCount);
51 }
52 }
53 }
4.使用匿名方法簡化一下委託操作
上面的代碼都很簡單,我們完全可以這麼寫:
1using System;
2using System.Threading;
3
4namespace 多線程
5 {
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Thread h = new Thread(delegate() {
11for (int i = 0; i < 8; i++)
12 {
13 Console.WriteLine("熱愛生活!");
14 }
15 });
16 h.Start();
17 Console.ReadKey();
18 }
19 }
20 }
好了,到了這裏大家應該對於線程有個初步認識了,當然微軟提供了特定的api用來設置某個線程綁定到特定的cpu上.有了上面的經驗,我們就來學習新的知識
阻塞與中斷線程
在上面的例子中我多次用到了sleep方法,地球人都知道它是用來掛起或者使線程休眠的。當然如果我們傳入的參數爲TimeOut.Infinit,那麼當前的線程就會永遠的休眠,知道被中斷或者終止爲止。也就意味着該線程被永久阻塞了。
這樣似乎也沒有什麼大不了的,但是問題出現了,當我們在另一個線程都對一個已經被阻塞的線程調用Thread.Interrupt方法,就會子啊被阻塞線程中引發ThreadInterruptedException的異常,同時將中斷被阻塞的線程,從而使該線程擺脫阻塞。也就是說我們可以在捕獲到異常後執行適當的操作可以繼續執行線程,但是如果我們忽略了該異常,則在在捕獲到異常將停止該異常。下面我們來演示一下效果:
1using System;
2using System.Threading;
3
4namespace 線程阻塞測試
5{
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Thread thread = new Thread(delegate() {
11 Console.WriteLine("線程休眠");
12try
13 {
14 Thread.Sleep(Timeout.Infinite);//永久休眠15 }
16catch(ThreadInterruptedException ex)
17 {
18 Console.WriteLine(ex.Message);
19for (int i = 0; i <= 8; i++)
20 {
21 Console.WriteLine("繼續執行線程"+Thread.CurrentThread.Name);
22 }
23 Console.WriteLine("線程執行完成!");
24 }
25 });
26 thread.Name = "測試線程";
27 Console.WriteLine("啓動線程" + Thread.CurrentThread.Name);
28 thread.Start();
29 thread.Interrupt();
30 Console.WriteLine("此處該線程已被中斷");
31 Console.ReadKey();
32 }
33 }
34 }
效果:
終止線程
從上面的例子我們得知了線程中斷的特點,但是不要把線程的中斷和終止混淆,這完全是連個不同的狀態,下面我們先上代碼,再做分析:
1using System;
2using System.Threading;
3
4namespace 終止線程
5{
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Thread myThread = new Thread(delegate()
11 {
12try
13 {
14for (int i = 0; i < 100; i++)
15 {
16 Console.WriteLine("線程正在正常運行!");
17 Thread.Sleep(50);//這樣可以執行2次循環18 }
19 }
20catch (ThreadAbortException e)
21 {
22 Console.WriteLine("捕獲線程異常.");
23 Console.WriteLine("異常消息: {0}", e.Message);
24Thread.ResetAbort();//防止再次引發該異常25 }
26 Console.WriteLine("當前線程{0}狀態{1},正在運行", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState.ToString());
27 Thread.Sleep(1000);
28 Console.WriteLine("模擬一些線程清理操作");
29 });
30 myThread.Start();
31 Thread.Sleep(100);//把時間片讓給myThread
32 Console.WriteLine("終止當前線程");
33 myThread.Abort();
34 myThread.Join();
35 Console.WriteLine("線程終止");
36
37 Console.ReadKey();
38 }
39 }
40 }
運行效果如下:
從上面我們可以看出線程終止不會立即退出,我們還可以在try語句的catch和finally塊中捕獲該異常,再做一些清理工作,這裏是清理而不是類似於線程中斷後的繼續執行,這個需要區分一下,其中上面代碼中的Thread.ResetAbort();這一句是相當重要的,如果沒有這一句,就會在catch塊的結尾處重新引發一個異常,爲了確定這個事實,我們對代碼再做一些修改,把異常拋到外層去,做個小測試,之後再看有什麼特點。
1using System;
2using System.Threading;
3
4namespace 終止線程
5{
6class Program
7 {
8staticvoid Main(string[] args)
9 {
10 Thread myThread = new Thread(delegate()
11 {
12try13 {
14try
15 {
16for (int i = 0; i < 100; i++)
17 {
18 Console.WriteLine("線程正在正常運行!");
19 Thread.Sleep(50);//這樣可以執行2次循環20 }
21 }
22catch (ThreadAbortException e)
23 {
24 Console.WriteLine("內層捕獲線程異常.");
25 Console.WriteLine("內層異常消息: {0}", e.Message);
26// Thread.ResetAbort();//防止再次引發該異常27 }
28
29 Console.WriteLine("當前線程{0}狀態{1},正在運行", Thread.CurrentThread.Name, Thread.CurrentThread.ThreadState.ToString());
30 Thread.Sleep(1000);
31 Console.WriteLine("模擬一些線程清理操作");
32 }
33catch (ThreadAbortException e)
34 {
35 Console.WriteLine("外層捕獲線程異常.");
36 Console.WriteLine("外層捕獲異常消息: {0}", e.Message);
37 }
38 });
39 myThread.Start();
40 Thread.Sleep(100);//把時間片讓給myThread
41 Console.WriteLine("終止當前線程");
42 myThread.Abort();
43 myThread.Join();
44 Console.WriteLine("線程終止");
45
46 Console.ReadKey();
47 }
48 }
49 }
運行效果如下:
可以看到,catch的結尾的確引發了一個異常,而且catch塊之後的語句都沒有執行。
當然我們還可以附加一個finally塊,來處理之後的catch塊之後的語句。
未完待續。。。