無論您是為具有單個(gè)處理器的計(jì)算機(jī)還是為具有多個(gè)處理器的計(jì)算機(jī)進(jìn)行開發(fā),您都希望應(yīng)用程序?yàn)橛脩籼峁┳詈玫捻憫?yīng)性能,即使應(yīng)用程序當(dāng)前正在完成其他工作。要使應(yīng)用程序能夠快速響應(yīng)用戶操作,同時(shí)在用戶事件之間或者甚至在用戶事件期間利用處理器,最強(qiáng)大的方式之一是使用多線程技術(shù)。
多線程:線程是程序中一個(gè)單一的順序控制流程.在單個(gè)程序中同時(shí)運(yùn)行多個(gè)線程完成不同的工作,稱為多線程。如果某個(gè)線程進(jìn)行一次長延遲操作, 處理器就切換到另一個(gè)線程執(zhí)行。這樣,多個(gè)線程的并行(并發(fā))執(zhí)行隱藏了長延遲,提高了處理器資源利用率,從而提高了整體性能。多線程是為了同步完成多項(xiàng)任務(wù),不是為了提高運(yùn)行效率,而是為了提高資源使用效率來提高系統(tǒng)的效率
一、進(jìn)程與線程
進(jìn)程,是操作系統(tǒng)進(jìn)行資源調(diào)度和分配的基本單位。是由進(jìn)程控制塊、程序段、數(shù)據(jù)段三部分組成。一個(gè)進(jìn)程可以包含若干線程(Thread),線程可以幫助應(yīng)用程序同時(shí)做幾件事(比 如一個(gè)線程向磁盤寫入文件,另一個(gè)則接收用戶的按鍵操作并及時(shí)做出反應(yīng),互相不干擾),在程序被運(yùn)行后中,系統(tǒng)首先要做的就是為該程序進(jìn)程建立一個(gè)默認(rèn)線程,然后程序可 以根據(jù)需要自行添加或刪除相關(guān)的線程。它是可并發(fā)執(zhí)行的程序。在一個(gè)數(shù)據(jù)集合上的運(yùn)行過程,是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,也是稱活動(dòng)、路徑或任務(wù),它有兩方面性質(zhì):活動(dòng)性、并發(fā)性。進(jìn)程可以劃分為運(yùn)行、阻塞、就緒三種狀態(tài),并隨一定條件而相互轉(zhuǎn)化:就緒--運(yùn)行,運(yùn)行--阻塞,阻塞--就緒。
線程(thread),線程是CPU調(diào)度和執(zhí)行的最小單位。有時(shí)被稱為輕量級(jí)進(jìn)程(Lightweight Process,LWP),是程序執(zhí)行流的最小單元。一個(gè)標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源。一個(gè)線程可以創(chuàng)建和撤消另一個(gè)線程,同一進(jìn)程中的多個(gè)線程之間可以并發(fā)執(zhí)行。由于線程之間的相互制約,致使線程在運(yùn)行中呈現(xiàn)出間斷性。線程也有就緒、阻塞和運(yùn)行三種基本狀態(tài)。
主線程,進(jìn)程創(chuàng)建時(shí),默認(rèn)創(chuàng)建一個(gè)線程,這個(gè)線程就是主線程。主線程是產(chǎn)生其他子線程的線程,同時(shí),主線程必須是最后一個(gè)結(jié)束執(zhí)行的線程,它完成各種關(guān)閉其他子線程的操作。盡管主線程是程序開始時(shí)自動(dòng)創(chuàng)建的,它也可以通過Thead類對(duì)象來控制,通過調(diào)用CurrentThread方法獲得當(dāng)前線程的引用
多線程的優(yōu)勢(shì):進(jìn)程有獨(dú)立的地址空間,同一進(jìn)程內(nèi)的線程共享進(jìn)程的地址空間。啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。
二、多線程優(yōu)點(diǎn)
1、提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時(shí)長的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。
2、使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。
3、改善程序結(jié)構(gòu)。一個(gè)既長又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。。
多線程盡管優(yōu)勢(shì)明顯,但是線程并發(fā)沖突、同步以及管理跟蹤,可能給系統(tǒng)帶來很多不確定性,這些必須引起足夠重視。
廢話不多說開始我們的多線程之旅。
三、多線程的應(yīng)用場(chǎng)合:
簡單總結(jié)了一下,一般有兩種情況:
1)多個(gè)線程,完成同類任務(wù),提高并發(fā)性能
2)一個(gè)任務(wù)有多個(gè)獨(dú)立的步驟,多個(gè)線程并發(fā)執(zhí)行各子任務(wù),提高任務(wù)處理效率
四、案例--搬運(yùn)工
在我們現(xiàn)實(shí)生活中,經(jīng)常看到這樣的場(chǎng)景。有一堆貨物,有幾個(gè)搬運(yùn)工負(fù)責(zé)將貨物搬運(yùn)到指定地點(diǎn)。但是搬運(yùn)工能力不同,有人一次能搬多箱,有人走路比較慢,搬運(yùn)一趟的時(shí)間間隔比較長。搬運(yùn)工,各自搬運(yùn),無先后,互不干擾。我們?nèi)绾卧诔绦蛑袑?shí)現(xiàn)這種場(chǎng)景呢?
案例分析:
這個(gè)就是最簡單的多線程的實(shí)際案例。每個(gè)人相當(dāng)于一個(gè)線程,并發(fā)執(zhí)行。當(dāng)貨物搬運(yùn)完畢后,每個(gè)線程自動(dòng)停止。這里暫時(shí)不考慮死鎖情況。
案例代碼:
namespace MutiThreadSample.Transport
{
/// <summary>
/// 搬運(yùn)工
/// </summary>
public class Mover
{
/// <summary>
/// 總數(shù)
/// </summary>
public static int GoodsTotal { get; set; }
/// <summary>
/// 間隔時(shí)間
/// </summary>
public static int IntervalTime { get; set; }
/// <summary>
/// 名稱
/// </summary>
public string Name { get; set; }
/// <summary>
/// 單位時(shí)間搬運(yùn)量
/// </summary>
public int LaborAmount { get; set; }
/// <summary>
/// 搬運(yùn)
/// </summary>
public void Move()
{
while (GoodsTotal > 0)
{
GoodsTotal -= LaborAmount;
Console.WriteLine("搬運(yùn)者:{0} 于 {1} 搬運(yùn)貨物 {2}",this.Name,DateTime.Now.Millisecond,this.LaborAmount);
Thread.Sleep(IntervalTime);
Console.WriteLine("搬運(yùn)者:{0} Continue",this.Name);
}
}
/// <summary>
/// 搬運(yùn)
/// </summary>
/// <param name="interval">時(shí)間間隔</param>
public void Move(object interval)
{
int tempInterval = 0;
if (!int.TryParse(interval.ToString(), out tempInterval))
{
tempInterval = IntervalTime;
}
while (GoodsTotal > 0)
{
GoodsTotal -= LaborAmount;
Console.WriteLine("搬運(yùn)者:{0} 于 {1} 搬運(yùn)貨物 {2}", this.Name, DateTime.Now.Millisecond, this.LaborAmount);
Thread.Sleep(tempInterval);
}
}
}
}
測(cè)試:
namespace MutiThreadSample.Transport
{
/// <summary>
/// 測(cè)試搬運(yùn)
/// </summary>
public class TestMove
{
/// <summary>
/// 搬運(yùn)
/// </summary>
public static void Move()
{
//測(cè)試搬運(yùn)工
Mover.GoodsTotal = 200;
Mover.IntervalTime = 10;
Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 };
Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 };
Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 };
List<Mover> movers = new List<Mover>();
movers.Add(m1);
//movers.Add(m2);
//movers.Add(m3);
if (movers != null && movers.Count > 0)
{
foreach (Mover m in movers)
{
Thread thread = new Thread(new ThreadStart(m.Move));
thread.Start();
}
}
//Main Thread continue
// validate Thread.Sleep()
//int i =0;
//int j = 0;
//while (i < 10)
//{
// while(j<10000000)
// {
// j++;
// }
// Console.WriteLine("CurrentThread:{0}", Thread.CurrentThread.Name);
// i++;
//}
}
/// <summary>
/// 搬運(yùn)
/// </summary>
public static void MoveWithParamThread()
{
//測(cè)試搬運(yùn)工
Mover.GoodsTotal = 1000;
Mover.IntervalTime = 100;
Mover m1 = new Mover() { Name = "Tom", LaborAmount = 5 };
Mover m2 = new Mover() { Name = "Jim", LaborAmount = 10 };
Mover m3 = new Mover() { Name = "Lucy", LaborAmount = 20 };
List<Mover> movers = new List<Mover>();
movers.Add(m1);
movers.Add(m2);
movers.Add(m3);
if (movers != null && movers.Count > 0)
{
foreach (Mover m in movers)
{
Thread thread = new Thread(new ParameterizedThreadStart(m.Move));
thread.Start(10);
}
}
}
}
}
通過案例我們也接觸了Thread,下面我們將詳細(xì)介紹Thread的功能。
五、Thread
創(chuàng)建并控制線程,設(shè)置其優(yōu)先級(jí)并獲取其狀態(tài)。
常用方法:
Start()
導(dǎo)致操作系統(tǒng)將當(dāng)前實(shí)例的狀態(tài)更改為 ThreadState.Running。
一旦線程處于 ThreadState.Running 狀態(tài),操作系統(tǒng)就可以安排其執(zhí)行。 線程從方法的第一行(由提供給線程構(gòu)造函數(shù)的 ThreadStart 或 ParameterizedThreadStart 委托表示)開始執(zhí)行。線程一旦終止,它就無法通過再次調(diào)用 Start 來重新啟動(dòng)。
Thread.Sleep()
調(diào)用 Thread.Sleep 方法會(huì)導(dǎo)致當(dāng)前線程立即阻止,阻止時(shí)間的長度等于傳遞給 Thread.Sleep 的毫秒數(shù),這樣,就會(huì)將其時(shí)間片中剩余的部分讓與另一個(gè)線程。 一個(gè)線程不能針對(duì)另一個(gè)線程調(diào)用 Thread.Sleep。
Interrupt()
中斷處于 WaitSleepJoin 線程狀態(tài)的線程。
Suspend和Resume(已過時(shí))
掛起和繼續(xù)
在 .NET Framework 2.0 版中,Thread.Suspend 和 Thread.Resume 方法已標(biāo)記為過時(shí),并將從未來版本中移除。
Abort()
方法用于永久地停止托管線程。一旦線程被中止,它將無法重新啟動(dòng)。
Join()
阻塞調(diào)用線程,直到某個(gè)線程終止時(shí)為止。
ThreadPriority(優(yōu)先級(jí))
指定 Thread 的調(diào)度優(yōu)先級(jí)。
ThreadPriority 定義一組線程優(yōu)先級(jí)的所有可能值。線程優(yōu)先級(jí)指定一個(gè)線程相對(duì)于另一個(gè)線程的相對(duì)優(yōu)先級(jí)。
每個(gè)線程都有一個(gè)分配的優(yōu)先級(jí)。在運(yùn)行庫內(nèi)創(chuàng)建的線程最初被分配 Normal 優(yōu)先級(jí),而在運(yùn)行庫外創(chuàng)建的線程在進(jìn)入運(yùn)行庫時(shí)將保留其先前的優(yōu)先級(jí)。可以通過訪問線程的 Priority 屬性來獲取和設(shè)置其優(yōu)先級(jí)。
根據(jù)線程的優(yōu)先級(jí)調(diào)度線程的執(zhí)行。用于確定線程執(zhí)行順序的調(diào)度算法隨操作系統(tǒng)的不同而不同。操作系統(tǒng)也可以在用戶界面的焦點(diǎn)在前臺(tái)和后臺(tái)之間移動(dòng)時(shí)動(dòng)態(tài)地調(diào)整線程的優(yōu)先級(jí)。
一個(gè)線程的優(yōu)先級(jí)不影響該線程的狀態(tài);該線程的狀態(tài)在操作系統(tǒng)可以調(diào)度該線程之前必須為 Running。
六、創(chuàng)建線程方式
通過搬運(yùn)工案例我們能夠了解線程的工作原理,也明白了線程的創(chuàng)建方式。
其實(shí)在C#中創(chuàng)建線程有幾種方式,這里給大家舉幾個(gè)常用例子,如下:
namespace MutiThreadSample
{
/// <summary>
/// 創(chuàng)建線程的方式
/// </summary>
class CreateThread
{
/// <summary>
/// 不帶參數(shù)的委托
/// </summary>
public void CreateThreadWithThreadStart()
{
Thread thread = new Thread(new ThreadStart(ThreadCallBack));
thread.Start();
}
/// <summary>
/// 帶參數(shù)的委托
/// </summary>
public void CreateThreadWithParamThreadStart()
{
Thread thread = new Thread(new ParameterizedThreadStart(ThreadCallBackWithParam));
thread.Start();
}
/// <summary>
/// 匿名函數(shù)
/// </summary>
public void CreateThreadWithAnonymousFunction()
{
Thread thread = new Thread(delegate()
{
Console.WriteLine("進(jìn)入子線程1");
for (int i = 1; i < 4; ++i)
{
Thread.Sleep(50);
Console.WriteLine("/t+++++++子線程1+++++++++");
}
Console.WriteLine("退出子線程1");
});
thread.Start();
}
/// <summary>
/// 直接賦值委托
/// </summary>
public void CreateThreadWithCallBack()
{
Thread _hThread = new Thread(ThreadCallBack);
_hThread.Start();
}
/// <summary>
/// 無參數(shù)的方法調(diào)用
/// </summary>
public void ThreadCallBack()
{
// Do Something
}
/// <summary>
/// 帶參數(shù)的方法
/// </summary>
/// <param name="obj"></param>
public void ThreadCallBackWithParam(object obj)
{
// Do Something
}
}
}
時(shí)鐘線程
使用 TimerCallback 委托指定希望 Timer 執(zhí)行的方法。 計(jì)時(shí)器委托在構(gòu)造計(jì)時(shí)器時(shí)指定,并且不能更改。 此方法不在創(chuàng)建計(jì)時(shí)器的線程上執(zhí)行,而是在系統(tǒng)提供的 ThreadPool 線程上執(zhí)行。創(chuàng)建計(jì)時(shí)器時(shí),可以指定在第一次執(zhí)行方法之前等待的時(shí)間量(截止時(shí)間)以及此后的執(zhí)行期間等待的時(shí)間量(時(shí)間周期)。 可以使用 Change 方法更改這些值或禁用計(jì)時(shí)器。
class TimerExample
{
static void Main()
{
// Create an event to signal the timeout count threshold in the
// timer callback.
AutoResetEvent autoEvent = new AutoResetEvent(false);
StatusChecker statusChecker = new StatusChecker(10);
// Create an inferred delegate that invokes methods for the timer.
TimerCallback tcb = statusChecker.CheckStatus;
// Create a timer that signals the delegate to invoke
// CheckStatus after one second, and every 1/4 second
// thereafter.
Console.WriteLine("{0} Creating timer./n",
DateTime.Now.ToString("h:mm:ss.fff"));
Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250);
// When autoEvent signals, change the period to every
// 1/2 second.
autoEvent.WaitOne(5000, false);
stateTimer.Change(0, 500);
Console.WriteLine("/nChanging period./n");
// When autoEvent signals the second time, dispose of
// the timer.
autoEvent.WaitOne(5000, false);
stateTimer.Dispose();
Console.WriteLine("/nDestroying timer.");
}
}
class StatusChecker
{
private int invokeCount;
private int maxCount;
public StatusChecker(int count)
{
invokeCount = 0;
maxCount = count;
}
// This method is called by the timer delegate.
public void CheckStatus(Object stateInfo)
{
AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
Console.WriteLine("{0} Checking status {1,2}.",
DateTime.Now.ToString("h:mm:ss.fff"),
(++invokeCount).ToString());
if(invokeCount == maxCount)
{
// Reset the counter and signal Main.
invokeCount = 0;
autoEvent.Set();
}
}
}
.Net的公用語言運(yùn)行時(shí)(Common Language Runtime,CLR)能區(qū)分兩種不同類型的線程:前臺(tái)線程和后臺(tái)線程。這兩者的區(qū)別就是:應(yīng)用程序必須運(yùn)行完所有的前臺(tái)線程才可以退出;而對(duì)于后臺(tái)線程,應(yīng)用程序則可以不考慮其是否已經(jīng)運(yùn)行完畢而直接退出,所有的后臺(tái)線程在應(yīng)用程序退出時(shí)都會(huì)自動(dòng)結(jié)束。
一個(gè)線程是前臺(tái)線程還是后臺(tái)線程可由它的IsBackground屬性來決定。這個(gè)屬性是可讀又可寫的。它的默認(rèn)值為false,即意味著一個(gè)線程默認(rèn)為前臺(tái)線程。
我們可以將它的IsBackground屬性設(shè)置為true,從而使之成為一個(gè)后臺(tái)線程。下面的例子是一個(gè)控制臺(tái)程序,程序一開始便啟動(dòng)了10個(gè)線程,每個(gè)線程運(yùn)行5秒鐘時(shí)間。由于線程的IsBackground屬性默認(rèn)為false,即它們都是前臺(tái)線程,所以盡管程序的主線程很快就運(yùn)行結(jié)束了,但程序要到所有已啟動(dòng)的線程都運(yùn)行完畢才會(huì)結(jié)束。示例代碼如下例子中的Test()所示
namespace MutiThreadSample.ThreadType
{
class ThreadTypeTest
{
/// <summary>
/// 測(cè)試前臺(tái)線程
/// </summary>
public static void Test()
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(new ThreadStart(ThreadFunc));
thread.Start();
}
}
/// <summary>
/// 測(cè)試后臺(tái)線程
/// </summary>
public static void TestBackgroundThread()
{
for (int i = 0; i < 10; i++)
{
Thread thread = new Thread(new ThreadStart(ThreadFunc));
thread.IsBackground = true;
thread.Start();
}
}
public static void ThreadFunc()
{
Thread.Sleep(0);
DateTime start = DateTime.Now;
while ((DateTime.Now - start).Seconds < 20);//可以停頓的時(shí)間長一點(diǎn),效果更加明顯
}
}
}
接下來我們對(duì)上面的代碼進(jìn)行略微修改,將每個(gè)線程的IsBackground屬性都設(shè)置為true,則每個(gè)線程都是后臺(tái)線程了。那么只要程序的主線程結(jié)束了,整個(gè)程序也就結(jié)束了。示例代碼如代碼中的TestBackgroundThread()。
這個(gè)例子直接創(chuàng)建一個(gè)控制臺(tái)程序即可檢驗(yàn)。
前臺(tái)和后臺(tái)線程的使用原則
既然前臺(tái)線程和后臺(tái)線程有這種差別,那么我們?cè)趺粗涝撊绾卧O(shè)置一個(gè)線程的IsBackground屬性呢?下面是一些基本的原則:對(duì)于一些在后臺(tái)運(yùn)行的線程,當(dāng)程序結(jié)束時(shí)這些線程沒有必要繼續(xù)運(yùn)行了,那么這些線程就應(yīng)該設(shè)置為后臺(tái)線程。比如一個(gè)程序啟動(dòng)了一個(gè)進(jìn)行大量運(yùn)算的線程,可是只要程序一旦結(jié)束,那個(gè)線程就失去了繼續(xù)存在的意義,那么那個(gè)線程就該是作為后臺(tái)線程的。而對(duì)于一些服務(wù)于用戶界面的線程往往是要設(shè)置為前臺(tái)線程的,因?yàn)榧词钩绦虻闹骶€程結(jié)束了,其他的用戶界面的線程很可能要繼續(xù)存在來顯示相關(guān)的信息,所以不能立即終止它們。這里我只是給出了一些原則,具體到實(shí)際的運(yùn)用往往需要編程者的進(jìn)一步仔細(xì)斟酌。
八、總結(jié)
這一章主要介紹多線程技術(shù)的基本知識(shí)。涉及多線程的具體應(yīng)用,包括預(yù)防死鎖、線程同步、線程池等,在今后的文章會(huì)涉及到。
新聞熱點(diǎn)
疑難解答
圖片精選