從公交塞車看C#多線程同步問(wèn)題
好久沒(méi)寫博客了,可能是因?yàn)樽罱ぷ魈^(guò)于壓抑的原因吧!有點(diǎn)頹廢了.... 而且公司距離住處要坐公交將近40--50分鐘(各個(gè)原因,糾結(jié)中ing...),提前一個(gè)半小時(shí)起床,居然還能遲到!因?yàn)榫嚯x公司前兩站是個(gè)十字路口,每天能在哪里塞上30多分鐘....眼看就要到公司了,一輛接一輛阻塞著..看著時(shí)間一分一秒的過(guò)去..心里不盡拔涼起來(lái)(遲到會(huì)扣錢的!)... 拔涼之余不禁讓我想到C#中線程的同步異步.所以呈此博文,來(lái)談?wù)勎覍?duì)C#中線程同步的理解,不當(dāng)之處,請(qǐng)大家多多指點(diǎn),在此先謝謝了!
什么是線程同步? 多個(gè)線程同時(shí)運(yùn)行時(shí),可能會(huì)因?yàn)榫€程之間的邏輯關(guān)系而決定誰(shuí)先執(zhí)行,誰(shuí)后執(zhí)行, 這就是線程同步。
(1)線程的優(yōu)先級(jí)
當(dāng)線程之間爭(zhēng)奪CPU時(shí)間片時(shí),CPU是按照線程的優(yōu)先級(jí)進(jìn)行服務(wù)的。在C#應(yīng)用程序中,線程有5個(gè)不同的優(yōu)先級(jí),由高到低分別是:Highest、AboveNormal、Normal 、BelowNormal和Lowest。創(chuàng)建線程時(shí),如果不指定其優(yōu)先級(jí),則系統(tǒng)默認(rèn)為Normal。
如:
Thread t = new Thread(MethodName);
t.priority = ThreadPriority.AboveNormal;
通過(guò)設(shè)置線程的優(yōu)先級(jí)可以改變線程執(zhí)行的順序,所設(shè)置的優(yōu)先級(jí)僅僅適用于這些線程所屬的進(jìn)程。(注:當(dāng)把某個(gè)線程的優(yōu)先級(jí)設(shè)置為Htghest時(shí),系統(tǒng)上正在運(yùn)行的其他線程都會(huì)終止,所以使用這個(gè)優(yōu)先級(jí)的時(shí)候要特別小心。除非遇到“幣需”馬上處理的任務(wù),否則不要使用這個(gè)優(yōu)先級(jí))。
(2)線程同步
多線程處理解決了吞吐量和響應(yīng)速度的問(wèn)題,但同時(shí)也帶來(lái)了資源共享問(wèn)題,如死鎖和資源爭(zhēng)用。多線程特別適用于需要不同的資源(如文件句柄和網(wǎng)絡(luò)連接)的任務(wù)。為單個(gè)資源分配多個(gè)線程可能會(huì)導(dǎo)致同步問(wèn)題,這種情況下,線程可能會(huì)北頻繁阻止以等待其他線程,從而使用多線程的初衷背道而馳。
所謂同步,是指多個(gè)線程之間存在先后執(zhí)行順序的關(guān)聯(lián)關(guān)系。如果一個(gè)線程必須在另一個(gè)線程完成某個(gè)工作后才能繼續(xù)執(zhí)行,則必須考慮如何讓其他保持同步,以確保在系統(tǒng)上同時(shí)運(yùn)行多個(gè)線程而不會(huì)出現(xiàn)死鎖或邏輯錯(cuò)誤。
為了解決同步問(wèn)題,一般使用輔助線程執(zhí)行不需要大量占用其他線程所使用的資源的耗時(shí)任務(wù)或時(shí)間要求緊迫的任務(wù)。但實(shí)際上,程序中的某些資源必須由多個(gè)線程訪問(wèn)。為了解決這些問(wèn)題,System.Threading命名空間提供了多個(gè)用于同步線程的類。這些類包括Mutex,Monitor,Interlocked,AutoResetEvent. 但是實(shí)際應(yīng)用程序中,我們使用最多的可能不是這些類,而是C#提供的lock語(yǔ)句。
Lock語(yǔ)句
為了在多線程應(yīng)用程序中讓同步變得簡(jiǎn)單,C#專門提供了一個(gè)lock語(yǔ)句。lock關(guān)鍵字能確保當(dāng)一個(gè)線程位于代碼的臨界區(qū)時(shí),另一個(gè)線程不進(jìn)入臨界區(qū)。如果其他線程試圖進(jìn)入鎖定的代碼段,則它將一直等待(阻塞),知道鎖定的對(duì)象被釋放以后才能進(jìn)入臨界區(qū)。
- private Object obj = new Object();
- .....
- lock(obj)
- {
- //臨界區(qū)
- }
舉個(gè)例子相信大家會(huì)更明白,路人甲和路人乙要上廁所,剛好找到了一個(gè)公共廁所,杯具的是公共廁所里面只有一個(gè)位置,路人甲是會(huì)員(優(yōu)先級(jí)高),先溜進(jìn)去了,然后把門鎖上(Lock)緊接著里面發(fā)出一陣陣巨響....(大家都懂的,最近食物不敢亂吃啊 - -!)。路人乙可著急了,捂著肚子,在外面打轉(zhuǎn),憋得面紅耳赤!過(guò)了好一段時(shí)間,路人甲抽著香煙,吹著口哨,從廁所里面走出來(lái)(Lock解鎖了),路人乙急忙鉆進(jìn)去,緊接著又是一陣巨響.....
雖然這個(gè)例子舉的有點(diǎn)不和諧,但相信大家已經(jīng)弄明白Lock的作用了。
值得注意的是:1、鎖定的對(duì)象名(上面的obj),一般聲明為Object類型,注意不要將其聲明為值類型,對(duì)象名叫什么無(wú)所謂,只要符合對(duì)象命名原則就行。2、一定要將該Object類型的對(duì)象名聲明為private(私有),不能將其聲明為public(公共),否則將會(huì)使lock語(yǔ)句無(wú)法控制,從而引發(fā)一系列的問(wèn)題。 (就像上面舉例一樣:漆黑不見(jiàn)五指的夜晚(沒(méi)電),路人乙解開(kāi)褲帶,正準(zhǔn)備蹲下時(shí),一只手把路人乙的PP托住,喊道:有人!- -#。)3、處于臨界區(qū)的代碼不宜太多。如果在鎖定和解鎖期間處理的代碼過(guò)多,由于某個(gè)線程執(zhí)行臨界區(qū)中的代碼時(shí),其他等待運(yùn)行臨界區(qū)中代碼的線程都會(huì)處于阻塞狀態(tài),這樣就可能會(huì)降低應(yīng)用程序的性能。(路人乙會(huì)恨死路人甲的!)
好了,閑話就說(shuō)這么多,還是拿代碼說(shuō)事吧,說(shuō)過(guò)隨機(jī)取款的例子。
(1)新建一個(gè)名為L(zhǎng)ockExample的Windows應(yīng)用程序,放下一個(gè)listbox,一個(gè)button,界面如下:

(2)添加一個(gè)類:Account.cs。代碼如下:
- class Account
- {
- private Object obj = new object();
- int balance;
- Random rd = new Random();
- Form1 form1;
- public Account(int initial,Form1 form1)
- {
- this.form1 = form1;
- this.balance = initial;
- }
- /// <summary>
- /// Withdraws the specified amount.
- /// </summary>
- /// <param name="amount">The amount.</param>
- /// <returns></returns>
- private int Withdraw(int amount)
- {
- if (balance < 0)
- {
- form1.AddListBoxItem("余額" + balance + " 兄弟,你以為你這是信用卡??!還錢吧!");
- }
- //將lock(lockedobj)這句話注視掉,看看會(huì)發(fā)生什么情況
- lock (obj)
- {
- if (balance >= amount)
- {
- string str = Thread.CurrentThread.Name + "取款---";
- str += string.Format("取款前余額:{0,-6}取款:{1,-6}",balance,amount);
- balance = balance - amount;
- str += "取款前余額:" + balance;
- form1.AddListBoxItem(str);
- return amount;
- }
- else
- {
- return 0;
- }
- }
- }
- public void DoTransactions()
- {
- for (int i = 0; i < 100; i++)
- {
- Withdraw(rd.Next(1,100));
- }
- }
- }
(3)切換到Form1.cs代碼編輯界面,寫入一下代碼:
- public partial class Form1 : Form
- {
- public Form1()
- {
- InitializeComponent();
- }
- private void btnLock_Click(object sender, EventArgs e)
- {
- lbLock.Items.Clear();
- Thread[] threads = new Thread[10];
- Account acc = new Account(1000,this);
- for (int i = 0; i < 10; i++)
- {
- Thread t = new Thread(acc.DoTransactions);
- t.Name = "線程" + i;
- threads[i] = t;
- }
- for (int i = 0; i < 10; i++)
- {
- threads[i].Start();
- }
- }
- delegate void AddListBoxItemDelegate(string str);
- public void AddListBoxItem(string str)
- {
- if (lbLock.InvokeRequired)
- {
- AddListBoxItemDelegate d = AddListBoxItem;
- lbLock.Invoke(d, str);
- }
- else
- {
- lbLock.Items.Add(str);
- }
- }
- }
(4)按<F5>編譯并運(yùn)行,單擊 “開(kāi)始自動(dòng)隨機(jī)取款”按鈕,觀察線程執(zhí)行后在listbox中添加的可能出現(xiàn)的內(nèi)容,如圖:

(5) 將lock(obj)這條語(yǔ)句注視掉,再次運(yùn)行程序,觀察線程執(zhí)行后在listbox中添加的可能出現(xiàn)的內(nèi)容,如圖:

如圖中線程6取款后余額已經(jīng)是584了,但是在線程7取款時(shí)候 余額又變成了746.顯然結(jié)果不正確。
好了,對(duì)線程同步問(wèn)題和解決同步問(wèn)題的理解,就先寫道這里,上述不當(dāng)之處,希望大家多多指點(diǎn),共同進(jìn)步。
原文:http://www.cnblogs.com/axing/archive/2011/08/25/lock.html
【編輯推薦】