法律聲明:本文章受到知識產權法保護,任何單位或我的若須要轉載此文,必需保證文章的完整性(未經做者許可的任何刪節或改動將視爲侵權行爲)。文章出處請務必註明51cto和CSDN以保障網站的權益,文章做者姓名請務必保留,並向
[email]bladey@tom.com[/email]發送郵件,標明文章位置及用途。轉載時請將此法律聲明一併轉載,謝謝!
鎖·二則
做 者:劉鐵猛
日 期:2005-12-25
關鍵字:lock 多線程 同步
小序
鎖者,lock關鍵字也。市面上的書雖然多,但仔細介紹這個keyword的書太少了。MSDN裏有,但所給的代碼很是零亂,讓人不能參透其中的玄機。昨天是平安夜,今天天然就是聖誕節了,沒別的什麼事情,因而整理了一下思路,使用兩個例子給你們講解一下lock關鍵字的使用和一點線程同步的問題。
一.基礎鋪墊——進程與線程
閱讀提示:若是您已經瞭解什麼是線程以及如何使用線程,只關心如何使用lock關鍵字,請跳過1、2、三節,直接登頂。
多線程(multi-thread)程序是lock關鍵字的用武之地。若是想編寫多線程的程序,通常都要在程序的開頭導入System.Threading這個名稱空間。
通常初學者在看書的時候,一看到多線程一章就會跳過去,一是認爲本身的小宇宙不夠強、功力不夠深——線程好神祕、好詭異——仍是先練好天馬流星拳以後再來收拾它;二是不瞭解何時要用到線程,認爲本身還用不到那麼高深的技術。其實呢,線程是個即簡單又經常使用的概念。
1.線程的載體——進程
要想知道什麼是線程,就不得不先了解一下線程的載體——進程。
咱們的程序在沒有運行的時候,都是以可執行文件(*.exe文件)的形式靜靜地躺在硬盤裏的。Windows下的可執行文件稱爲PE文件格式(Portable Executable File Format),這裏的Portable不是指Portable Computer(便攜式電腦,也就是本本)中的「便攜」,而是指在全部Windows系統上均可以執行的便捷性、可移植性。可執行文件會一直那麼靜靜地躺着,直到你用鼠標敲兩下它的腦殼——這時候,Windows的程序管理功能就會根據程序的特徵爲程序分配必定的內存空間,並使用加載器(loader)把程序體裝載入內存。不過值得注意的是,這個時候程序雖然已經被裝載入了內存,但尚未執行起來。接下來Windows會尋找程序的「入口點」以開始執行它。固然,咱們都已經知道——若是是命令行應用程序,那麼它的入口點是main函數,若是是GUI(Graphic User Interface,簡言之就是帶窗×××互界面一類的)應用程序,那麼它的入口點將是_tWinMain函數(早先叫WinMain,參閱本人另外一篇拙文《一個Win32程序的進化》)。一旦找到這個入口點函數,操做系統就要「調用」這個主函數,程序就從這裏進入執行了。同時,系統會把開始執行的應用程序註冊爲一個「進程」(Process),欲知系統運行着多少進程,你能夠按下Ctrl+Alt+Del,從Task Manager裏查看(若是對Windows如何管理進程感興趣,能夠閱讀與分時系統、搶先式多任務相關的文章和書籍)。
至此,咱們能夠說:若是把一個應用程序當作是一個Class的話,那麼進程就是這個Class在內存中的一個「活」的實例——這是面向對象的理念。如今或許你也應該明白了爲何C#語言的Main函數要寫在一個Class裏,而不能像C/C++那樣把main函數赤裸裸地寫在外面。類是能夠有多個實例的,一個程序也能夠經過被雙擊N次在內存中運行N個副本,咱們經常使用的Word 200三、QQ等都是這樣的程序。固然,也有的程序只容許在內存裏有一個實例,MSN Messenger和殺毒軟件就是是這樣的一類。
2.主角登場——線程
一個進程只作一件事情,這本無可非議,但無奈人老是貪心的。人們但願應用程序一邊作着前臺的程序,一邊在後臺默默無聞地幹着其它工做。線程的出現,真可謂「將多任務進行到底」了。
這兒有幾個實際應用的例子。好比我在用Word杜撰老闆交給的命題(這是Word的主線程),個人Word就在後臺爲我計時,而且每10分鐘爲我自動保存一次,以便在發生地震以後我能快速找回十分鐘以前寫的稿子並繼續工做——死不了仍是要交的。抑或是個人Outlook,它一邊看我向手頭的郵件裏狠命堆諸如「預算正常」「進展順利」之類的字眼,一邊不緊不慢地在後臺接收別人發給個人債務單和催命會議通知……它哪裏知道我是多麼想到Out去look一下,透透氣。諸此IE,MSN Messenger,QQ,Flashget,BT,eMule……盡數是基於多線程而得以生存的軟件。如今,咱們應該已經意識到,基本上稍微有點用的程序就得用到多線程——特別是在網絡應用程序中,使用多線程格外重要。
二.進程和線程間的關係
咱們已經在感觀上對進程和線程有了初步的瞭解,那麼它們之間有什麼關係呢?前面咱們已經提到一點,那就是——進程是線程的載體,每一個進程至少包含一個線程。接下來,咱們來看看其它的關係。
1.進程與進程的關係:在.NET平臺下,每一個應用程序都被load進入本身獨立的內存空間內,這個內存空間稱爲Application Domain,簡稱爲AppDomain。一個一個AppDomain就像一個一個小隔間,把進程與進程、進程與系統底層之間隔絕起來,以防某個程序忽然發瘋的話會殃及近鄰或者形成系統崩潰。
2.線程與線程的關係:在同一個進程內能夠存在不少線程,與進程同時啓動的那個線程是主線程。非主線程不可能本身啓動,必定是直接或間接由主線程啓動的。線程與線程之間能夠相互通訊,共同使用某些資源。每一個線程具備本身的優先級,優先級高的先執行,低的後執行。衆線之間的關係非有趣——若是它們之間是互相獨立、誰也不用顧及誰的話,那麼就是「非同步狀態」(Unsynchronized),比較像路上的行人;而若是線程與線程之間是相互協同協做甚至是依賴的,那麼就是「同步狀態」(Synchronized),這與反恐特警執行Action同樣,須要互相配合,毫不能一哄而上——投×××不能像招聘會上投簡歷那樣!
三.線程小例
這裏給出一個C#寫的多線程的小范例,若是有哪裏不明白,請參閱MSDN。我在之後的文章中將仔細講解這些小例子。
//==============================================//
// 水之真諦 //
// //
//
[url]http://blog.csdn.net/FantasiaX[/url] //
// //
// 上善若水潤物無聲 //
//==============================================//
using System;
using System.Threading;//多線程程序必需的
namespace ThreadSample1
{
class A
{
//爲了可以做爲線程的入口點,程序必需是無參、無返回值
public static void Say()
{
for (int i = 0; i < 1000; i++)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("A merry Christmas to you!");
}
}
}
class B
{
//爲了可以做爲線程的入口點,程序必需是無參、無返回值
public void Say()
{
for (int i = 0; i < 1000; i++)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("A merry Christmas to you!");
}
}
}
class Program
{
static void Main(string[] args)
{
//用到兩個知識點:A類的靜態方法;匿名的ThreadStart實例
//若是想了解如何構造一個線程(Thread)請查閱MSDN
Thread Thread1 = new Thread(new ThreadStart(A.Say));
B b = new B();
//此次是使用實例方法來構造一個線程
Thread Thread2 = new Thread(new ThreadStart(b.Say));
//試試把下面兩句同時註釋掉,會發生什麼結果?
Thread2.Priority = ThreadPriority.Highest;
Thread1.Priority = ThreadPriority.Lowest;
Thread1.Start();
Thread2.Start();
}
}
}
這個例子徹底是爲了咱們講解lock而作的鋪墊,但願你們必定要仔細讀懂。其中最重要的是理解由靜態方法和實例方法構造線程。還要注意到,本例中使用到了線程的優先級:Thread2的優先級爲最高,Thread1的優先級爲最低,因此儘管Thread1比Thread2先啓動,而要等到Thread2執行完以後再執行(線程優先級有5級,你們能夠本身動手試一試)。若是把給兩個線程賦優先級的語句註釋掉,你會發現兩種顏色交錯在一塊兒……這就體現出了線程間的「非同步狀態」。注意:在沒有註釋掉兩句以前,兩個線程雖然有前後順序,但那是由優先級(操做系統)決定的,不能算是同步(線程間相互協同)。
四.登頂
很抱歉的一點是,lock的使用與線程的同步是相關的,而本文限於篇幅又不能對線程同步加以詳述。本人將在近期再寫一篇專門記述線程同步的文章,在此以前,請你們先參閱MSDN及其餘同仁的做品。
1.使用lock關鍵字的第一個目的:保證共享資源的安全。
當多個線程共享一個資源的時候,經常會產生協同問題,這樣的協同問題每每是因爲時間延遲引發的。拿銀行的ATM機舉例,若是裏面有可用資金5000元,每一個人每次能夠取50到200元,如今有100我的來取錢。假設一我的取錢的時候,ATM機與銀行數據庫的溝通時間爲10秒,那麼在與總行計算機溝通完畢以前(也就是把你取的錢從可用資金上扣除以前),ATM機不能再接受別一我的的請求——也就是被「鎖定」。這也就是lock關鍵字得名的緣由。
若是不「鎖定」ATM會出現什麼狀況呢?假設ATM裏只剩下100元了,後面還有不少人等着取錢,一我的取80,ATM驗證80<100成立,因而吐出80,這時須要10秒鐘與銀行總機經過網絡溝通(網絡,特別是爲了保證安全的網絡,老是有延遲的),因爲沒有鎖定ATM,後面的客戶也打算取80……戲劇性的一幕出現了:ATM又吐出來80!由於這時候它仍然認爲本身肚子裏有100元!下面的程序就是這個例子的完整實現。
這個例子同時也展示了lock關鍵第的第一種用法:針對由靜態方法構造的線程,因爲線程所執行的方法並不具備類的實例做爲載體,因此,「上鎖」的時候,只能是鎖這個靜態方法所在的類——lock (typeof(ATM))
//======================================================//
// 水之真諦 //
// //
//
[url]http://blog.csdn.net/FantasiaX[/url] //
// //
// 上善若水潤物無聲 //
//======================================================//
using System;
using System.Threading;
namespace LockSample
{
class ATM
{
static int remain = 5000;//可用金額
public static void GiveOutMoney(int money)
{
lock (typeof(ATM))//核心代碼!註釋掉這句,會獲得紅色警報
{
if (remain >= money)
{
Thread.Sleep(100);//模擬時間延遲
remain -= money;
}
}
if (remain >= 0)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("{0}$ \t in ATM.", remain);
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine("{0}$ \t remained.", remain);
}
}
}
class Boy
{
Random want = new Random();
int money;
public void TakeMoney()
{
money = want.Next(50, 200);
ATM.GiveOutMoney(money);
}
}
class Program
{
static void Main(string[] args)
{
Boy[] Boys = new Boy[100];
Thread[] Threads = new Thread[100];
for (int i = 0; i < 100; i++)
{
Boys[i] = new Boy();
Threads[i] = new Thread(new ThreadStart(Boys[i].TakeMoney));
Threads[i].Name = "Boy" + i.ToString();
Threads[i].Start();
}
}
}
}
2.使用lock關鍵字的第二個目的:保證線程執行的順序合理。
回想上面的例子:取錢這件事情基本上能夠認爲是一個操做就能完成,而不少事情並非一步就能完成的,特別是若是每一步都與某個共享資源掛鉤時,若是在一件事情完成(好比十個操做步驟)以前不把資源鎖進來,那麼N多線程亂用資源,確定會混亂不堪的。相反,若是咱們在一套完整操做完成以前可以鎖定資源(保證使用者的「獨佔性」),那麼想使用資源的N多線程也就變得井井有理了。
狗年快到了,讓咱們來看看咱們的狗媽媽是怎樣照顧她的小寶貝的。狗媽媽「花花」有三個小寶貝,它們的身體情況不太相同:壯壯很壯,老是搶別人的奶吃;靈靈體格通常,搶不到先也不會餓着;笨笨就比較笨了,身體弱,老是喝不着奶。這一天,狗媽媽決定改善一下給小寶貝們餵奶的方法——由原來的哄搶方式改成一狗喂十口,先喂笨笨,而後是靈靈,最後纔是壯壯……在一隻小狗狗吮完十口以前,別的小狗狗不準來搗蛋!OK,讓咱們看下面的代碼:
注意,這段代碼展現了lock的第二種用法——針對由實例方法構造的線程,lock將鎖住這個方法的實例載體,也就是使用了——lock (this)
//======================================================//
// 水之真諦 //
// //
//
[url]http://blog.csdn.net/FantasiaX[/url] //
// //
// 上善若水潤物無聲 //
//======================================================//
using System;
using System.Threading;
namespace LockSample2
{
class DogMother
{
//喂一口奶
void Feed()
{
//Console.ForegroundColor = ConsoleColor.Yellow;
//Console.WriteLine("Puzi...zi...");
//Console.ForegroundColor = ConsoleColor.White;
Thread.Sleep(100);//喂一口奶的時間延遲
}
//每隻狗狗喂口奶
public void FeedOneSmallDog()
{
//由於用到了實例方法,因此要鎖this,this是本類運行時的實例
//註釋掉下面一行,回到哄搶方式,線程的優先級將產生效果
lock (this)
{
for (int i = 1; i <= 10; i++)
{
this.Feed();
Console.WriteLine(Thread.CurrentThread.Name.ToString() + " sucked {0} time.", i);
}
}
}
}
class Program
{
static void Main(string[] args)
{
DogMother huahua = new DogMother();
Thread DogStrong = new Thread(new ThreadStart(huahua.FeedOneSmallDog));
DogStrong.Name = "Strong small Dog";
DogStrong.Priority = ThreadPriority.AboveNormal;
Thread DogNormal = new Thread(new ThreadStart(huahua.FeedOneSmallDog));
DogNormal.Name = "Normal small Dog";
DogNormal.Priority = ThreadPriority.Normal;
Thread DogWeak = new Thread(new ThreadStart(huahua.FeedOneSmallDog));
DogWeak.Name = "Weak small Dog";
DogWeak.Priority = ThreadPriority.BelowNormal;
//因爲lock的使用,線程的優先級就沒有效果了,保證了順序的合理性
//註釋掉lock句後,線程的優先級將再次顯現效果
DogWeak.Start();
DogNormal.Start();
DogStrong.Start();
}
}
}
小結:
祝賀你!至此,咱們已經初步學會了如何使用C#語言的lock關鍵字來使一組共享同一資源的線程進行同步、保證執行順序的合理以及共享資源的安全。
相信若是你已經仔細看過例子,並在本身的機器上進行了實踐,那麼你對線程已經再也不陌生、懼怕(就像小宇宙爆發了同樣)。若是你不知足於僅僅是學會一個lock,還想掌握更多更高超的技能(好比……呃……六道輪迴?柔破斬?無敵風火輪?如來神掌?),請參閱MSDN中System.Threading名稱空間的內容,你會發現lock背後隱藏的祕密(Monitor 類),並且我也極力推薦你這麼作:趁熱打鐵,你能夠了解到爲何lock只能對引用類型加以使用、lock與Monitor的Enter/Exit和Try…Catch是如何互換的……
五.祝願
今天是聖誕節呢,祝全部看到本文的XDJM聖誕節快樂!新年也快樂!
再過幾天,新年也要到啦!狗狗年!我喜歡……祝全部明年是本命年的XDJM生活幸福,萬事如意!噢~~~~~對了,明年是大狗高立的本命年,借這當先祝你吉祥如意啦!
六.祈福
我有一個好朋友,小迪,心臟不太好……明年還要動大手術。在這離上帝最近的日子裏,我爲她祈禱:小迪,祝你平安!一切都會好起來的!我祈求上帝:當我在聖誕節的時候把我知道的知識全力以赴講給每個願意聽的人時,算不算是幫您作事呢?若是是……請您賜予小迪以健康。我一樣祈求每一個讀到這裏的朋友:請您也爲個人朋友祝福一下,謝謝。
法律聲明:本文章受到知識產權法保護,任何單位或我的若須要轉載此文,必需保證文章的完整性(未經做者許可的任何刪節或改動將視爲侵權行爲)。文章出處請務必註明51cto和CSDN以保障網站的權益,文章做者姓名請務必保留,並向
[email]bladey@tom.com[/email]發送郵件,標明文章位置及用途。轉載時請將此法律聲明一併轉載,謝謝!