多線程概念
什么是進程?
當一個程序開始運行時,它就是一個進程,進程包括運行中的程序和程序所使用到的內(nèi)存和系統(tǒng)資源。而一個進程又是由多個線程所組成的。
什么是線程?
線程是程序中的一個執(zhí)行流,每個線程都有自己的專有寄存器(棧指針、程序計數(shù)器等),但代碼區(qū)是共享的,即不同的線程可以執(zhí)行同樣的函數(shù)。
什么是多線程?
多線程是指程序中包含多個執(zhí)行流,即在一個程序中可以同時運行多個不同的線程來執(zhí)行不同的任務,也就是說允許單個程序創(chuàng)建多個并行執(zhí)行的線程來完成各自的任務。
多線程的好處:
可以提高 CPU 的利用率。在多線程程序中,一個線程必須等待的時候, CPU 可以運行其它的線程而不是等待,這樣就大大提高了程序的效率。
多線程的不利方面:
線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多; 多線程需要協(xié)調(diào)和管理,所以需要 CPU 時間跟蹤線程; 線程之間對共享資源的訪問會相互影響,必須解決競用共享資源的問題;線程太多會導致控制太復雜,最終可能造成很多Bug;
實例
C#編程中的多線程機制進行探討。為了省去創(chuàng)建 GUI 那些繁瑣的步驟,更清晰地逼近線程的本質(zhì),接下來的所有程序都是控制臺程序,程序最后的Console.ReadLine()是為了使程序中途停下來,以便看清楚執(zhí)行過程中的輸出。
任何程序在執(zhí)行時,至少有一個主線程。
using System;
using System.Threading;
namespace ThreadTest
{
? ? class Program
? ? {
? ? ? ? static void Main(string[] args)
? ? ? ? {
? ? ? ? ? ? Thread.CurrentThread.Name = "System Thread";//給當前線程起名為"System Thread"
? ? ? ? ? ? Console.WriteLine(Thread.CurrentThread.Name + "'Status:" + Thread.CurrentThread.ThreadState);
? ? ? ? ? ? Console.ReadLine();
? ? ? ? }
? ? }
}
輸出如下:
System Thread's Status:Running
在這里,我們通過 Thread 類的靜態(tài)屬性 CurrentThread 獲取了當前執(zhí)行的線程,對其 Name 屬性賦值“SystemThread”,最后還輸出了它的當前狀態(tài)( ThreadState)。
CurrentThread 是類Thread的靜態(tài)屬性 具體可查看API文檔
所有與多線程機制應用相關(guān)的類都是放在System.Threading 命名空間中的
Thread 類來創(chuàng)建和控制線程, ThreadPool 類用于管理線程
池等。(此外還提供解決了線程執(zhí)行安排,死鎖,線程間通訊等實際問題的機制。)
如何操縱一個線程:
Thread 類有幾個至關(guān)重要的方法,描述如下:
Start():啟動線程;
Sleep(int):靜態(tài)方法,暫停當前線程指定的毫秒數(shù);
Abort():通常使用該方法來終止一個線程;
Suspend():該方法并不終止未完成的線程,它僅僅掛起線程,以后還可恢復;
Resume():恢復被 Suspend()方法掛起的線程的執(zhí)行。
Join():阻塞調(diào)用線程,直到某個線程終止時為止。
創(chuàng)建線程
使用 Thread 類創(chuàng)建線程時, 只需提供線程入口
即可。(線程入口使程序知道該讓這個線程干什么事)
創(chuàng)建線程的語法:
Thread newThread = new Thread(方法);
注意:方法可以是靜態(tài)的也可以為非靜態(tài)
方法可以是本類的也可以為非本類
思考:如果線程的入口方法帶參數(shù)怎么辦?
線程的參數(shù)請由對象或類進行承載
線程的執(zhí)行
在 C#中,線程入口是通過 ThreadStart 代理( delegate)來提供的,你可以把
ThreadStart理解為一個委托,指向線程要執(zhí)行的函數(shù),當調(diào)用Thread.Start()
方法后,線程就開始執(zhí)行 ThreadStart 所代表或者說指向的函數(shù)。
語法:
Thread(自定義的線程對象的方法).Start();
線程的休眠
使用靜態(tài)方法 Thread.Sleep(n)可以讓線程休眠
n表示休眠的毫秒數(shù)
阻塞調(diào)用線程
使用方法 自定義線程.Join()可以讓調(diào)用線程阻塞
阻塞的時間為某線程終止為止
終止線程
使用方法 自定義線程.Abort()可以讓線程終止
終止的線程無法再次啟動
線程的狀態(tài)
Thread.ThreadState 屬性
這個屬性代表了線程運行時狀態(tài),在不同的情況下有不同的值,我們有時候可以通過對該值的判斷來設計程序流程。
ThreadState 屬性的取值如下:
Aborted:線程已停止;
AbortRequested:線程的 Thread.Abort()方法已被調(diào)用,但是線程還未停止;
Background:線程在后臺執(zhí)行,與屬性 Thread.IsBackground 有關(guān);
Running:線程正在正常運行;
Stopped:線程已經(jīng)被停止;
StopRequested:線程正在被要求停止;
Suspended:線程已經(jīng)被掛起(此狀態(tài)下,可以通過調(diào)用 Resume()方法重新運
行);
SuspendRequested:線程正在要求被掛起,但是未來得及響應;
Unstarted:未調(diào)用 Thread.Start()開始線程的運行;
WaitSleepJoin:線程因為調(diào)用了 Wait(),Sleep()或 Join()等方法處于封鎖狀態(tài);
上面提到了 Background 狀態(tài)表示該線程在后臺運行,那么后臺運行的線程有什么特別的地方呢?其實后臺線程跟前臺線程只有一個區(qū)別,那就是后臺線程不妨礙程序的終止。一旦一個進程所有的前臺線程都終止后, CLR(通用語言運行環(huán)境)將通過調(diào)用任意一個存活中的后臺進程的 Abort()方法來徹底終止進程。
線程的優(yōu)先級
當線程之間爭奪 CPU 時間時, CPU 是按照線程的優(yōu)先級給予服務的。
在 C#應用程序中,用戶可以設定5個不同的優(yōu)先級
由高到低:
Highest,AboveNormal,Normal, BelowNormal, Lowest
在創(chuàng)建線程時如果不指定優(yōu)先級,那么系統(tǒng)默認為 ThreadPriority.Normal。
給一個線程指定優(yōu)先級,我們可以使用如下代碼:
//設定優(yōu)先級為最低
myThread.Priority=ThreadPriority.Lowest;
通過設定線程的優(yōu)先級,我們可以安排一些相對重要的線程優(yōu)先執(zhí)行,例如對用戶的響應等等。
自動管理
實際開發(fā)中使用的線程往往是大量的和更為復雜的,這時,每次都創(chuàng)建線程、啟動線程。從性能上來講,這樣做并不理想(因為每使用一個線程就要創(chuàng)建一個,需要占用系統(tǒng)開銷);從操作上來講,每次都要啟動,比較麻煩。為此引入的線程池的概念。
其實“線程池”就是用來存放“線程”的對象池。
在程序中,如果某個創(chuàng)建某種對象所需要的代價太高,同時這個對象又可以反復使用,那么我們往往就會準備一個容器,用來保存一批這樣的對象。于是乎,我們想要用這種對象時,就不需要每次去創(chuàng)建一個,而直接從容器中取出一個現(xiàn)成的對象就可以了。由于節(jié)省了創(chuàng)建對象的開銷,程序性能自然就上升了。這個容器就是“池”。很容易理解的是,因為有了對象池,因此在用完對象之后必須有一個“歸還”的動作,這樣便可以把對象放回池中,下次需要的時候就可以再次拿出來使用了。
線程池的作用:因為創(chuàng)建一個線程的代價較高,因此我們使用線程池設法復用線程。就是這么簡單。
線程互斥
lock 鎖代碼塊
class Program
{
? ? static Object obj = new object();
? ? static void Main(string[] args)
? ? {
? ? ? ? // 本類方法的線程
? ? ? ? // 靜態(tài)方法的線程
? ? ? ? Thread currentClassThread = new Thread(ThreadStaticMethod);
? ? ? ? currentClassThread.Name = "thread1";
? ? ? ? Thread currentClassThread1 = new Thread(ThreadStaticMethod);
? ? ? ? currentClassThread1.Name = "thread2";
? ? ? ? // 自定義的線程啟動
? ? ? ? currentClassThread.Start();
? ? ? ? currentClassThread1.Start();
? ? ? ? // 主線程繼續(xù)執(zhí)行
? ? ? ? Console.WriteLine("Main線程執(zhí)行");
? ? }
? ? public static void ThreadStaticMethod()
? ? {
? ? ? ? lock(obj)
? ? ? ? {? ? ? ? ? ?
? ? ? ? ? ? for (int i = 0; i < 10; i++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //執(zhí)行
? ? ? ? ? ? ? ? Console.WriteLine(Thread.CurrentThread.Name + " ThreadStaticMethod執(zhí)行");
? ? ? ? ? ? ? ? Thread.Sleep(1000);
? ? ? ? ? ? }
? ? ? ? }
? ? }
}
Monitor類鎖定一個對象
class Program
{
? ? static Object obj = new object();
? ? static void Main(string[] args)
? ? {
? ? ? ? // 本類方法的線程
? ? ? ? // 靜態(tài)方法的線程
? ? ? ? Thread currentClassThread = new Thread(ThreadStaticMethod);
? ? ? ? currentClassThread.Name = "thread1";
? ? ? ? Thread currentClassThread1 = new Thread(ThreadStaticMethod);
? ? ? ? currentClassThread1.Name = "thread2";
? ? ? ? // 自定義的線程啟動
? ? ? ? currentClassThread.Start();
? ? ? ? currentClassThread1.Start();
? ? ? ? // 主線程繼續(xù)執(zhí)行
? ? ? ? Console.WriteLine("Main線程執(zhí)行");
? ? }
? ? public static void ThreadStaticMethod()
? ? {
? ? ? ? ? ? Monitor.Enter(obj);
? ? ? ? ? ? for (int i = 0; i < 10; i++)
? ? ? ? ? ? {
? ? ? ? ? ? ? ? //執(zhí)行
? ? ? ? ? ? ? ? Console.WriteLine(Thread.CurrentThread.Name + " ThreadStaticMethod執(zhí)行");
? ? ? ? ? ? ? ? Thread.Sleep(1000);
? ? ? ? ? ? }
? ? ? ? ? ? Monitor.Exit(obj);
? ? }
}