C#多線程的應用


1.進程

就像我們任務管理器里面運行的進程

 

進程(Process)是Windows系統中的一個基本概念,它包含着一個運行程序所需要的資源。一個正在運行的應用程序在操作系統中被視為一個進程,進程可以包括一個或多個線程。線程是操作系統分配處理器時間的基本單元,在進程中可以有多個線程同時執行代碼。進程之間是相對獨立的,一個進程無法訪問另一個進程的數據(除非利用分布式計算方式),一個進程運行的失敗也不會影響其他進程的運行,Windows系統就是利用進程把工作划分為多個獨立的區域的。進程可以理解為一個程序的基本邊界。是應用程序的一個運行例程,是應用程序的一次動態執行過程。

2.線程

我們也可以看到當前運行的總線程數

那么,線程是什么呢?

線程(Thread)是進程中的基本執行單元,是操作系統分配CPU時間的基本單位,一個進程可以包含若干個線程,在進程入口執行的第一個線程被視為這個進程的主線程。在.NET應用程序中,都是以Main()方法作為入口的,當調用此方法時系統就會自動創建一個主線程。線程主要是由CPU寄存器、調用棧和線程本地存儲器(Thread Local Storage,TLS)組成的。CPU寄存器主要記錄當前所執行線程的狀態,調用棧主要用於維護線程所調用到的內存與數據,TLS主要用於存放線程的狀態信息。

3.多線程

多線程的優點:可以同時完成多個任務;可以使程序的響應速度更快;可以讓占用大量處理時間的任務或當前沒有進行處理的任務定期將處理時間讓給別的任務;可以隨時停止任務;可以設置每個任務的優先級以優化程序性能。

為什么可以多線程執行呢?總結起來有下面兩方面的原因:

1、CPU運行速度太快,硬件處理速度跟不上,所以操作系統進行分時間片管理。這樣,從宏觀角度來說是多線程並發的,因為CPU速度太快,察覺不到,看起來是同一時刻執行了不同的操作。但是從微觀角度來講,同一時刻只能有一個線程在處理。

2、目前電腦都是多核多CPU的,一個CPU在同一時刻只能運行一個線程,但是多個CPU在同一時刻就可以運行多個線程。

然而,多線程雖然有很多優點,但是也必須認識到多線程可能存在影響系統性能的不利方面,才能正確使用線程。不利方面主要有如下幾點:

(1)線程也是程序,所以線程需要占用內存,線程越多,占用內存也越多。

(2)多線程需要協調和管理,所以需要占用CPU時間以便跟蹤線程。

(3)線程之間對共享資源的訪問會相互影響,必須解決爭用共享資源的問題。

(4)線程太多會導致控制太復雜,最終可能造成很多程序缺陷。

當啟動一個可執行程序時,將創建一個主線程。在默認的情況下,C#程序具有一個線程,此線程執行程序中以Main方法開始和結束的代碼,Main()方法直接或間接執行的每一個命令都有默認線程(主線程)執行,當Main()方法返回時此線程也將終止。

一個進程可以創建一個或多個線程以執行與該進程關聯的部分程序代碼。在C#中,線程是使用Thread類處理的,該類在System.Threading命名空間中。使用Thread類創建線程時,只需要提供線程入口,線程入口告訴程序讓這個線程做什么。通過實例化一個Thread類的對象就可以創建一個線程。創建新的Thread對象時,將創建新的托管線程。Thread類接收一個ThreadStart委托或ParameterizedThreadStart委托的構造函數,該委托包裝了調用Start方法時由新線程調用的方法,示例代碼如下:

Thread thread=new Thread(new ThreadStart(method));//創建線程

thread.Start();                                                           //啟動線程

上面代碼實例化了一個Thread對象,並指明將要調用的方法method(),然后啟動線程。ThreadStart委托中作為參數的方法不需要參數,並且沒有返回值。ParameterizedThreadStart委托一個對象作為參數,利用這個參數可以很方便地向線程傳遞參數,示例代碼如下:

Thread thread=new Thread(new ParameterizedThreadStart(method));//創建線程

thread.Start(3);                                                                             //啟動線程

創建多線程的步驟:
1、編寫線程所要執行的方法
2、實例化Thread類,並傳入一個指向線程所要執行方法的委托。(這時線程已經產生,但還沒有運行)
3、調用Thread實例的Start方法,標記該線程可以被CPU執行了,但具體執行時間由CPU決定

 

System.Threading.Thread類

Thread類是是控制線程的基礎類,位於System.Threading命名空間下,具有4個重載的構造函數:

名稱 說明
Thread(ParameterizedThreadStart)

初始化 Thread 類的新實例,指定允許對象在線程啟動時傳遞給線程的委托。要執行的方法是有參的。

Thread(ParameterizedThreadStart, Int32) 初始化 Thread 類的新實例,指定允許對象在線程啟動時傳遞給線程的委托,並指定線程的最大堆棧大小
Thread(ThreadStart)

初始化 Thread 類的新實例。要執行的方法是無參的。

Thread(ThreadStart, Int32)

初始化 Thread 類的新實例,指定線程的最大堆棧大小。

ThreadStart是一個無參的、返回值為void的委托。委托定義如下:

public delegate void ThreadStart()

通過ThreadStart委托創建並運行一個線程:

 class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //創建無參的線程
 6             Thread thread1 = new Thread(new ThreadStart(Thread1));
 7             //調用Start方法執行線程
 8             thread1.Start();
 9 
10             Console.ReadKey();
11         }
12 
13         /// <summary>
14         /// 創建無參的方法
15         /// </summary>
16         static void Thread1()
17         {
18             Console.WriteLine("這是無參的方法");
19         }
20     }

運行結果

除了可以運行靜態的方法,還可以運行實例方法

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //創建ThreadTest類的一個實例
 6             ThreadTest test=new ThreadTest();
 7             //調用test實例的MyThread方法
 8             Thread thread = new Thread(new ThreadStart(test.MyThread));
 9             //啟動線程
10             thread.Start();
11             Console.ReadKey();
12         }
13     }
14 
15     class ThreadTest
16     {
17         public void MyThread()
18         {
19             Console.WriteLine("這是一個實例方法");
20         }
21     }

運行結果

如果為了簡單,也可以通過匿名委托或Lambda表達式來為Thread的構造方法賦值

 1  static void Main(string[] args)
 2  {
 3        //通過匿名委托創建
 4        Thread thread1 = new Thread(delegate() { Console.WriteLine("我是通過匿名委托創建的線程"); });
 5        thread1.Start();
 6        //通過Lambda表達式創建
 7        Thread thread2 = new Thread(() => Console.WriteLine("我是通過Lambda表達式創建的委托"));
 8        thread2.Start();
 9        Console.ReadKey();
10  }

運行結果:

ParameterizedThreadStart是一個有參的、返回值為void的委托,定義如下:

public delegate void ParameterizedThreadStart(Object obj)

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             //通過ParameterizedThreadStart創建線程
 6             Thread thread = new Thread(new ParameterizedThreadStart(Thread1));
 7             //給方法傳值
 8             thread.Start("這是一個有參數的委托");
 9             Console.ReadKey();
10         }
11 
12         /// <summary>
13         /// 創建有參的方法
14         /// 注意:方法里面的參數類型必須是Object類型
15         /// </summary>
16         /// <param name="obj"></param>
17         static void Thread1(object obj)
18         {
19             Console.WriteLine(obj);
20         }
21     }

注意:ParameterizedThreadStart委托的參數類型必須是Object的。如果使用的是不帶參數的委托,不能使用帶參數的Start方法運行線程,否則系統會拋出異常。但使用帶參數的委托,可以使用thread.Start()來運行線程,這時所傳遞的參數值為null。

線程的常用屬性

屬性名稱 說明
CurrentContext 獲取線程正在其中執行的當前上下文。
CurrentThread 獲取當前正在運行的線程。
ExecutionContext 獲取一個 ExecutionContext 對象,該對象包含有關當前線程的各種上下文的信息。
IsAlive 獲取一個值,該值指示當前線程的執行狀態。
IsBackground 獲取或設置一個值,該值指示某個線程是否為后台線程。
IsThreadPoolThread 獲取一個值,該值指示線程是否屬於托管線程池。
ManagedThreadId 獲取當前托管線程的唯一標識符。
Name 獲取或設置線程的名稱。
Priority 獲取或設置一個值,該值指示線程的調度優先級。
ThreadState 獲取一個值,該值包含當前線程的狀態。

 

線程的標識符

ManagedThreadId是確認線程的唯一標識符,程序在大部分情況下都是通過Thread.ManagedThreadId來辨別線程的。而Name是一個可變值,在默認時候,Name為一個空值 Null,開發人員可以通過程序設置線程的名稱,但這只是一個輔助功能。

線程的優先級別

當線程之間爭奪CPU時間時,CPU按照線程的優先級給予服務。高優先級的線程可以完全阻止低優先級的線程執行。.NET為線程設置了Priority屬性來定義線程執行的優先級別,里面包含5個選項,其中Normal是默認值。除非系統有特殊要求,否則不應該隨便設置線程的優先級別。

成員名稱 說明
Lowest 可以將 Thread 安排在具有任何其他優先級的線程之后。
BelowNormal 可以將 Thread 安排在具有 Normal 優先級的線程之后,在具有 Lowest 優先級的線程之前。
Normal 默認選擇。可以將 Thread 安排在具有 AboveNormal 優先級的線程之后,在具有 BelowNormal 優先級的線程之前
AboveNormal 可以將 Thread 安排在具有 Highest 優先級的線程之后,在具有 Normal 優先級的線程之前。
Highest 可以將 Thread 安排在具有任何其他優先級的線程之前。

 

線程的狀態

通過ThreadState可以檢測線程是處於Unstarted、Sleeping、Running 等等狀態,它比 IsAlive 屬性能提供更多的特定信息。

前面說過,一個應用程序域中可能包括多個上下文,而通過CurrentContext可以獲取線程當前的上下文。

CurrentThread是最常用的一個屬性,它是用於獲取當前運行的線程。

 

System.Threading.Thread的方法

Thread 中包括了多個方法來控制線程的創建、掛起、停止、銷毀,以后來的例子中會經常使用。

方法名稱 說明
Abort()     終止本線程。
GetDomain() 返回當前線程正在其中運行的當前域。
GetDomainId() 返回當前線程正在其中運行的當前域Id。
Interrupt() 中斷處於 WaitSleepJoin 線程狀態的線程。
Join() 已重載。 阻塞調用線程,直到某個線程終止時為止。
Resume() 繼續運行已掛起的線程。
Start()   執行本線程。
Suspend() 掛起當前線程,如果當前線程已屬於掛起狀態則此不起作用
Sleep()   把正在運行的線程掛起一段時間。

線程實例

 1     static void Main(string[] args)
 2         {
 3             //獲取正在運行的線程
 4             Thread thread = Thread.CurrentThread;
 5             //設置線程的名字
 6             thread.Name = "主線程";
 7             //獲取當前線程的唯一標識符
 8             int id = thread.ManagedThreadId;
 9             //獲取當前線程的狀態
10             ThreadState state= thread.ThreadState;
11             //獲取當前線程的優先級
12             ThreadPriority priority= thread.Priority;
13             string strMsg = string.Format("Thread ID:{0}\n" + "Thread Name:{1}\n" +
14                 "Thread State:{2}\n" + "Thread Priority:{3}\n", id, thread.Name,
15                 state, priority);
16 
17             Console.WriteLine(strMsg);
18                       
19             Console.ReadKey();
20         }

運行結果:

 

前台線程和后台線程

前台線程:只有所有的前台線程都結束,應用程序才能結束。默認情況下創建的線程
              都是前台線程
后台線程:只要所有的前台線程結束,后台線程自動結束。通過Thread.IsBackground設置后台線程。必須在調用Start方法之前設置線程的類型,否則一旦線程運行,將無法改變其類型。

通過BeginXXX方法運行的線程都是后台線程。

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {                   
 5             //演示前台、后台線程
 6             BackGroundTest background = new BackGroundTest(10);
 7             //創建前台線程
 8             Thread fThread = new Thread(new ThreadStart(background.RunLoop));
 9             //給線程命名
10             fThread.Name = "前台線程";
11             
12 
13             BackGroundTest background1 = new BackGroundTest(20);
14             //創建后台線程
15             Thread bThread = new Thread(new ThreadStart(background1.RunLoop));
16             bThread.Name = "后台線程";
17             //設置為后台線程
18             bThread.IsBackground = true;
19 
20             //啟動線程
21             fThread.Start();
22             bThread.Start();
23         }
24     }
25 
26     class BackGroundTest
27     {
28         private int Count;
29         public BackGroundTest(int count)
30         {
31             this.Count = count;
32         }
33         public void RunLoop()
34         {
35             //獲取當前線程的名稱
36             string threadName = Thread.CurrentThread.Name;
37             for (int i = 0; i < Count; i++)
38             {
39                 Console.WriteLine("{0}計數:{1}",threadName,i.ToString());
40                 //線程休眠500毫秒
41                 Thread.Sleep(1000);
42             }
43             Console.WriteLine("{0}完成計數",threadName);
44             
45         }
46     }

運行結果:前台線程執行完,后台線程未執行完,程序自動結束。

把bThread.IsBackground = true注釋掉,運行結果:主線程執行完畢后(Main函數),程序並未結束,而是要等所有的前台線程結束以后才會結束。

后台線程一般用於處理不重要的事情,應用程序結束時,后台線程是否執行完成對整個應用程序沒有影響。如果要執行的事情很重要,需要將線程設置為前台線程。

 

線程同步

所謂同步:是指在某一時刻只有一個線程可以訪問變量。
如果不能確保對變量的訪問是同步的,就會產生錯誤。
c#為同步訪問變量提供了一個非常簡單的方式,即使用c#語言的關鍵字Lock,它可以把一段代碼定義為互斥段,互斥段在一個時刻內只允許一個線程進入執行,而其他線程必須等待。在c#中,關鍵字Lock定義如下:
Lock(expression)
{
   statement_block
}

expression代表你希望跟蹤的對象:
           如果你想保護一個類的實例,一般地,你可以使用this;
           如果你想保護一個靜態變量(如互斥代碼段在一個靜態方法內部),一般使用類名就可以了
而statement_block就算互斥段的代碼,這段代碼在一個時刻內只可能被一個線程執行。

 

以書店賣書為例:

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {                   
 5             BookShop book = new BookShop();
 6             //創建兩個線程同時訪問Sale方法
 7             Thread t1 = new Thread(new ThreadStart(book.Sale));
 8             Thread t2 = new Thread(new ThreadStart(book.Sale));
 9             //啟動線程
10             t1.Start();
11             t2.Start();
12             Console.ReadKey();
13         }
14     }
15 
16    
17 
18     class BookShop
19     {
20         //剩余圖書數量
21         public int num = 1;
22         public void Sale()
23         {
24             int tmp = num;
25             if (tmp > 0)//判斷是否有書,如果有就可以賣
26             {
27                 Thread.Sleep(1000);
28                 num -= 1;
29                 Console.WriteLine("售出一本圖書,還剩余{0}本", num);
30             }
31             else
32             {
33                 Console.WriteLine("沒有了");
34             }
35         }
36     }

運行結果:

從運行結果可以看出,兩個線程同步訪問共享資源,沒有考慮同步的問題,結果不正確。

 

考慮線程同步,改進后的代碼:

 1 class Program
 2     {
 3         static void Main(string[] args)
 4         {                   
 5             BookShop book = new BookShop();
 6             //創建兩個線程同時訪問Sale方法
 7             Thread t1 = new Thread(new ThreadStart(book.Sale));
 8             Thread t2 = new Thread(new ThreadStart(book.Sale));
 9             //啟動線程
10             t1.Start();
11             t2.Start();
12             Console.ReadKey();
13         }
14     }
15 
16    
17 
18     class BookShop
19     {
20         //剩余圖書數量
21         public int num = 1;
22         public void Sale()
23         {
24             //使用lock關鍵字解決線程同步問題
25             lock (this)
26             {
27                 int tmp = num;
28                 if (tmp > 0)//判斷是否有書,如果有就可以賣
29                 {
30                     Thread.Sleep(1000);
31                     num -= 1;
32                     Console.WriteLine("售出一本圖書,還剩余{0}本", num);
33                 }
34                 else
35                 {
36                     Console.WriteLine("沒有了");
37                 }
38             }
39         }
40     }

運行結果:

跨線程訪問:

https://www.cnblogs.com/dotnet261010/p/6159984.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM