14多线程程序设计
多線程程序設(shè)計(jì)
多線程程序設(shè)計(jì)
如果在一個(gè)程序中,有多個(gè)工作要同時(shí)做,可以采用多線程。在Windows操作系統(tǒng)中可以運(yùn)行多個(gè)程序,把一個(gè)運(yùn)行的程序叫做一個(gè)進(jìn)程。一個(gè)進(jìn)程又可以有多個(gè)線程,每個(gè)線程輪流占用CPU的運(yùn)行時(shí)間,Windows操作系統(tǒng)將時(shí)間分為時(shí)間片,一個(gè)線程使用一個(gè)時(shí)間片后,操作系統(tǒng)將此線程掛起,將另一個(gè)線程喚醒,使其使用下一個(gè)時(shí)間片,操作系統(tǒng)不斷的把線程掛起,喚醒,再掛起,再喚醒,如此反復(fù),由于現(xiàn)在CPU的速度比較快,給人的感覺象是多個(gè)線程同時(shí)執(zhí)行。Windows操作系統(tǒng)中有很多這樣的例子,例如復(fù)制文件時(shí),一方面在進(jìn)行磁盤的讀寫操作,同時(shí)一張紙不停的從一個(gè)文件夾飄到另一個(gè)文件夾,這個(gè)飄的動作實(shí)際上是一段動畫,兩個(gè)動作是在不同線程中完成的,也就是說兩個(gè)動作是同時(shí)完成的。又如Word程序中的拼寫檢查也是在另一個(gè)線程中完成的。每個(gè)進(jìn)程最少有一個(gè)線程,叫主線程,是進(jìn)程自動創(chuàng)建的,每進(jìn)程可以創(chuàng)建多個(gè)線程。
不同語言和操作系統(tǒng)對線程提供了不同支持,編寫多線程應(yīng)用程序的方法也不盡相同。例如,VB6沒有提供對線程的支持,程序員不能處理自己的線程。VC++6.0開發(fā)人員必須充分理解Windows線程和處理模型的復(fù)雜性,同時(shí)擁有這種線程模型的強(qiáng)大功能。
.Net Framework提供了一個(gè)完整而且功能強(qiáng)大的線程模型,該模型允許編程人員精確控制線程中運(yùn)行的內(nèi)容,線程何時(shí)退出,以及它將訪問多少數(shù)據(jù)等,但使用比VC++6.0簡單。
7.1?? 線程類(Thread)的屬性和方法
線程類在命名空間System.Threading中定義的,因此如果要創(chuàng)建多線程,必須引入命名空間System.Threading。Thread類的常用屬性和方法如下:
l? 屬性Priority:設(shè)置線程優(yōu)先級,有5種優(yōu)先級類別:AboveNormal(稍高)、BelowNormal(稍低)、Normal(中等,默認(rèn)值)、Highest(最高)和Lowest(最低)。例如語句myThread.Priority=ThreadPriority.Highest設(shè)置線程myThread的優(yōu)先級為最高。一個(gè)線程的優(yōu)先權(quán)并不是越高越好,應(yīng)考慮到整個(gè)進(jìn)程中所有線程以及其他進(jìn)程的情況做出最優(yōu)選擇。優(yōu)先級相同的線程按照時(shí)間片輪流運(yùn)行。優(yōu)先級高的線程先運(yùn)行,只有優(yōu)先級高的線程停止、休眠或暫停時(shí),低優(yōu)先級的線程才能運(yùn)行。
l? 構(gòu)造函數(shù):Thread(new ThreadStart(線程中要執(zhí)行的方法名)),構(gòu)造函數(shù)參數(shù)中指定的方法需要程序員自己定義,這個(gè)方法完成線程所要完成的任務(wù),退出該方法,線程結(jié)束。該方法必須為公有void類型的方法,不能有參數(shù)。
l? 方法Start():建立線程類對象后,線程處于未啟動狀態(tài),這個(gè)方法使線程改變?yōu)榫途w狀態(tài),如果能獲的CPU的運(yùn)行時(shí)間,線程變?yōu)檫\(yùn)行狀態(tài)。
l? 方法IsAlive():判斷線程對象是否存在,=true,線程存在。
l? 方法Abort():撤銷線程對象。不能撤銷一個(gè)已不存在的線程對象,因此在撤銷一個(gè)線程對象前,必須用方法IsAlive()判斷線程對象是否存在。
l? 靜態(tài)方法Sleep():線程休眠參數(shù)指定的時(shí)間,單位為毫秒,此時(shí)線程處于休眠狀態(tài)。線程休眠后,允許其它就緒線程運(yùn)行。休眠指定時(shí)間后,線程變?yōu)榫途w狀態(tài)。
l? 方法Suspend():該方法使線程變?yōu)閽炱馉顟B(tài)。必須用Resume()方法喚醒掛起線程。
l? 方法Resume():該方法使掛起線程變?yōu)榫途w狀態(tài),如果能獲的CPU的運(yùn)行時(shí)間,線程變?yōu)檫\(yùn)行狀態(tài)。如果線程多次被掛起,僅調(diào)用一次Resume()方法就可以把線程喚醒。
7.2?? 創(chuàng)建線程
例子e7_2:本例使用線程類Thread直接創(chuàng)建一個(gè)新的線程,在標(biāo)簽控件中顯示該線程運(yùn)行的時(shí)間。在窗體放置4個(gè)按鈕,單擊按鈕完成新建、掛起、恢復(fù)和停止線程的功能。
(1)??? 新建項(xiàng)目。在窗體中放置4個(gè)按鈕和一個(gè)標(biāo)簽控件,屬性Name分別為:button1、button2、button3、button4和label1,按鈕屬性Text分別為:新線程、掛起、恢復(fù)、撤銷。button1屬性Enabled=true,其余按鈕的屬性Enabled=false。
(2)??? 在Form1.cs頭部增加語句:using System.Threading。
(3)??? 為Form1類定義一個(gè)線程類變量:private Thread thread;
(4)??? 為標(biāo)題為"新線程"的按鈕(button1)增加單擊事件處理函數(shù)如下:
private void button1_Click(object sender, System.EventArgs e)
{?? thread=new Thread(new ThreadStart(fun));//生成線程類對象,fun為自定義方法
label1.Text="0";//運(yùn)行時(shí)間從0開始
thread.Start();//線程變?yōu)榫途w狀態(tài),如能獲的CPU運(yùn)行時(shí)間,線程變?yōu)檫\(yùn)行狀態(tài)
button1.Enabled=false;//標(biāo)題為"新線程"的按鈕,創(chuàng)建線程后,不允許再創(chuàng)建線程
button2.Enabled=true;//標(biāo)題為"掛起"的按鈕,允許對運(yùn)行狀態(tài)的線程掛起
button3.Enabled=false;//標(biāo)題為"恢復(fù)"的按鈕,線程未掛起,不能恢復(fù)
button4.Enabled=true;// 標(biāo)題為"撤銷"的按鈕,允許對運(yùn)行狀態(tài)的線程撤銷
}
(5)??? 為標(biāo)題為"掛起"的按鈕(button2)增加單擊事件處理函數(shù)如下:
private void button2_Click(object sender, System.EventArgs e)
{?? thread. Suspend();//線程暫停(掛起)
button1.Enabled=false;
button2.Enabled=false;
button3.Enabled=true;
button4.Enabled=false;
}
(6)??? 為標(biāo)題為"恢復(fù)"的按鈕(button3)增加單擊事件處理函數(shù)如下:
private void button3_Click(object sender, System.EventArgs e)
{?? thread. Resume();//暫停(掛起)線程恢復(fù)運(yùn)行
button1.Enabled=false;
button2.Enabled=true;
button3.Enabled=false;
button4.Enabled=true;
}
(7)??? 為標(biāo)題為"撤銷"的按鈕(button4)增加單擊事件處理函數(shù)如下:
private void button4_Click(object sender, System.EventArgs e)
{?? if(thread.IsAlive)
{?? thread.Abort();//撤銷線程對象
button1.Enabled=true;
button2.Enabled=false;
button3.Enabled=false;
button4.Enabled=false;
}
}
(8)??? C#線程模型允許將任何一個(gè)void類型的公有方法(靜態(tài)或非靜態(tài))作為線程方法,因此允許在任何一個(gè)類(不要求這個(gè)類是某個(gè)類的子類)中定義線程方法,而且同一個(gè)類中可以定義多個(gè)線程方法。為Form1類定義一個(gè)線程方法如下:
public void fun()//在線程中執(zhí)行的方法,必須為公有void類型方法,不能有參數(shù)。
{?? while(true)//退出該方法,線程結(jié)束,這里是死循環(huán),線程將一直運(yùn)行
{?? int x=Convert.ToInt32(label1.Text);
x++;
label1.Text=Convert.ToString(x);
Thread.Sleep(1000);//線程休眠1秒鐘,休眠一次,線程運(yùn)行了1秒鐘
}
}
(9)??? 在關(guān)閉程序之前,必須撤銷線程對象。為主窗體的Closing事件增加事件處理函數(shù)如下:
private void Form1_Closing(object sender,System.ComponentModel.CancelEventArgs e)
{?? if(thread.IsAlive)
? ? ? ? ? ? ? ? ? ? ? ? thread.Abort();
}
(10) 編譯,運(yùn)行,單擊標(biāo)題為"新線程"的按鈕,新線程開始,計(jì)數(shù)器從0開始計(jì)數(shù)。單擊標(biāo)題為"掛起"的按鈕,線程暫停,計(jì)數(shù)器也暫停。單擊標(biāo)題為"恢復(fù)"的按鈕,線程重新啟動,計(jì)數(shù)器也繼續(xù)計(jì)數(shù)。單擊標(biāo)題為"撤銷"的按鈕,線程對象被撤銷,線程對象不存在,計(jì)數(shù)器停止計(jì)數(shù)。運(yùn)行效果如右圖。
7.3?? 建立線程類
有時(shí)需要建立多個(gè)線程,每個(gè)線程要實(shí)現(xiàn)的功能基本相同,但有個(gè)別參數(shù)不同,例如,每個(gè)線程完成同樣的任務(wù),但控制的對象不同。使用線程類Thread直接創(chuàng)建新線程,線程類構(gòu)造函數(shù)參數(shù)為一個(gè)方法,在這個(gè)方法中實(shí)現(xiàn)線程所要求的任務(wù),但該方法不能有參數(shù),因此無法通過方法的參數(shù)傳遞不同設(shè)置。為解決這個(gè)問題,可以定義一個(gè)自己的線程類。具體實(shí)現(xiàn)方法見下例。下邊的例子用到了進(jìn)度條(ProgressBar)控件,首先介紹進(jìn)度條控件。
7.3.1? 進(jìn)度條(ProgressBar)控件
進(jìn)度條(ProgressBar)控件經(jīng)常用來顯示一個(gè)任務(wù)的進(jìn)度。有時(shí),要完成一個(gè)長時(shí)間的任務(wù),例如一個(gè)軟件的安裝,如果沒有任何提示,使用者可能分不清任務(wù)是在進(jìn)行中,還是死機(jī)了,可以使用進(jìn)度條顯示安裝進(jìn)度,表示安裝正在進(jìn)行。進(jìn)度條常用屬性如下:
l? 屬性Maximum:進(jìn)度條所代表的最大值(整數(shù)),默認(rèn)值100。
l? 屬性Minimum:進(jìn)度條所代表的最小值(整數(shù)),默認(rèn)值0。
l? 屬性Step:變化的步長,默認(rèn)值為10。
l? 屬性Value:進(jìn)度條當(dāng)前位置代表的值。修改該值,達(dá)到一個(gè)Step,進(jìn)度增加一格。
7.3.2? 用線程控制進(jìn)度條
例子e7_3_2:建立兩個(gè)線程,分別控制兩個(gè)進(jìn)度條(ProgressBar)控件,每個(gè)進(jìn)度條的屬性Value變化的速率不一樣。具體實(shí)現(xiàn)步驟如下,運(yùn)行效果如下圖。
(1)?? 新建項(xiàng)目。在Form1.cs頭部增加語句:using System.Threading。
(2)?? 在窗體中放置2個(gè)進(jìn)度條(ProgressBar)控件。屬性Name分別為progressBar1、progressBar2。
(3)?? 在Form1.cs文件e7_3_2命名空間中,Form1類定義的后邊,建立線程類如下:
public class myThread
{???? private int SleepTime;//線程的休眠時(shí)間,從構(gòu)造函數(shù)賦值
private ProgressBar progressBar;//本線程控制哪個(gè)進(jìn)度條,從構(gòu)造函數(shù)賦值
private Thread thread1;
public myThread(int Time,ProgressBar p1)//構(gòu)造函數(shù),
{???? SleepTime=Time;
progressBar=p1;
thread1=new Thread(new ThreadStart(fun));
thread1.Start();
}
public void fun()//在線程中執(zhí)行的方法,必須為公有void類型方法,不能有參數(shù)。
{?? while(progressBar.Value!=100)
{?? progressBar.Value+=1;
Thread.Sleep(SleepTime);
}
}//退出該方法,線程結(jié)束
}
(4)?? 為Form1類增加變量:myThread? myThread1,myThread2。
(5)?? 為Form1類構(gòu)造函數(shù)增加語句如下:
myThread1=new myThread(100,progressBar1);
myThread2=new myThread(200,progressBar2);
(6)?? 編譯,運(yùn)行,可以看到兩個(gè)進(jìn)度條以不同的速度前進(jìn),當(dāng)進(jìn)度條被添滿,線程停止。
7.4?? 多個(gè)線程互斥
多個(gè)線程同時(shí)修改共享數(shù)據(jù)可能發(fā)生錯(cuò)誤。假設(shè)2個(gè)線程分別監(jiān)視2個(gè)入口進(jìn)入的人數(shù),每當(dāng)有人通過入口,線程用C#語句對總?cè)藬?shù)變量執(zhí)行加1操作。一條C#語句可能包含若干機(jī)器語言語句,假設(shè)C#語句加1操作包含的機(jī)器語言語句是:先取總?cè)藬?shù),加1,再存回總?cè)藬?shù)。操作系統(tǒng)可以在一條機(jī)器語言語句結(jié)束后,掛起運(yùn)行的線程。如當(dāng)前總?cè)藬?shù)為5,線程1運(yùn)行,監(jiān)視到有人通過入口,取出總?cè)藬?shù)(此時(shí)為5)后,線程1時(shí)間用完掛起。線程2喚醒,也監(jiān)視到有人通過入口,并完成了總?cè)藬?shù)加1并送回的操作,總?cè)藬?shù)為6,線程2掛起。線程1喚醒,對已取出的總?cè)藬?shù)(此時(shí)為5)加1,存回總?cè)藬?shù),總?cè)藬?shù)應(yīng)為7,實(shí)際為6,少算一個(gè)。為了防止此類錯(cuò)誤,在一個(gè)線程修改共享資源(例如上例的總?cè)藬?shù)變量)時(shí),不允許其它線程對同一共享資源進(jìn)行修改,這叫線程的互斥。這樣的實(shí)例很多,例如計(jì)算機(jī)中的許多外設(shè),網(wǎng)絡(luò)中的打印機(jī)等都是共享資源,只允許一個(gè)進(jìn)程或線程使用。
7.4.1? 多個(gè)線程同時(shí)修改共享數(shù)據(jù)可能發(fā)生錯(cuò)誤
例子e7_4_1:下邊的例子模擬2個(gè)線程同時(shí)修改同一個(gè)共享數(shù)據(jù)時(shí)可能發(fā)生的錯(cuò)誤。
(1)?? 新建項(xiàng)目。在Form1.cs頭部增加語句:using System.Threading。
(2)?? 在窗體中放置一個(gè)標(biāo)簽控件,屬性Name=label1。
(3)?? 為Form1類定義2個(gè)線程類變量:Thread thread1,thread2。定義整形變量:int num=0。
(4)?? 為Form1類構(gòu)造函數(shù)增加語句如下:
thread1= new Thread(new ThreadStart(Fun1));
thread2= new Thread(new ThreadStart(Fun2));
thread1.Start();
thread2.Start();
(5)?? 為Form1類中定義Fun1()和Fun2()方法如下:
public void Fun1()//在線程中執(zhí)行的方法,必須為公有void類型方法,不能有參數(shù)。
{?? int k,n;
for(k=0;k<4;k++)
{?? n=num;//取出num,可以把把num想象為總?cè)藬?shù)
n++;//加1
Thread.Sleep(10);//模擬復(fù)雜的費(fèi)時(shí)運(yùn)算,在此期間,有可能時(shí)間片用完
num=n;//存回num
Thread.Sleep(50);
}
label1.Text=Convert.ToString(num);
}//退出該方法,線程結(jié)束
public void Fun2()
{?? int k,n;
for(k=0;k<4;k++)
{?? n=num;
n++;
Thread.Sleep(10);
num=n;
Thread.Sleep(100);
}
label1.Text=Convert.ToString(num);
}
(6)?? 編譯,運(yùn)行,標(biāo)簽控件應(yīng)顯示8,實(shí)際運(yùn)行多次,顯示的數(shù)要小于8。
7.4.2? 用Lock語句實(shí)現(xiàn)互斥
Lock語句的形式如下:lock(e){訪問共享資源的代碼}。其中e指定要鎖定的對象,必須是引用類型,一般為this,即Lock語句所在類的對象。Lock語句將訪問共享資源的代碼標(biāo)記為臨界區(qū)。臨界區(qū)的意義是:假設(shè)線程1正在執(zhí)行e對象的臨界區(qū)中的代碼時(shí),如其它線程也要求執(zhí)行這個(gè)e對象的任何臨界區(qū)中代碼,將被阻塞,一直到線程1退出臨界區(qū)。
例子e7_4_2:用C#語句Lock實(shí)現(xiàn)互斥。修改例子e7_4_1中的Fun1()和Fun2()方法如下:
public void Fun1()//在線程中執(zhí)行的方法,必須為公有void類型方法,不能有參數(shù)。
{?? int k,n;
for(k=0;k<4;k++)
{?? lock(this)//這里的this是Form1類的對象
{?? n=num;//這對大括號中代碼為this的臨界區(qū)
n++;//this的臨界區(qū)包含兩部分,函數(shù)Fun1和Fun2中的臨界區(qū)
Thread.Sleep(10);
num=n;
}
Thread.Sleep(50);
}
label1.Text=Convert.ToString(num);
}//退出該方法,線程結(jié)束
public void Fun2()
{?? int k,n;
for(k=0;k<4;k++)
{?? lock(this)//如有線程進(jìn)入此臨界區(qū),其它線程就不能進(jìn)入這個(gè)臨界區(qū)
{?? n=num;//也不能進(jìn)入前邊的臨界區(qū)
n++;
Thread.Sleep(10);
num=n;
}
Thread.Sleep(100);
}
label1.Text=Convert.ToString(num);
}
編譯,運(yùn)行,標(biāo)簽控件顯示8。如果有多個(gè)共享數(shù)據(jù)區(qū),使用此方法不太方便。
7.4.3? 用Mutex類實(shí)現(xiàn)互斥
可以使用Mutex類對象保護(hù)共享資源(如上例中的總?cè)藬?shù)變量)不被多個(gè)線程同時(shí)訪問。Mutex類WaitOne方法和ReleaseMutex方法之間代碼是互斥體,這些代碼要訪問共享資源。Mutex類的WaitOne方法分配互斥體訪問權(quán),該方法只向一個(gè)線程授予對互斥體的獨(dú)占訪問權(quán)。如果一個(gè)線程獲取了互斥體,則要獲取該互斥體的第二個(gè)線程將被掛起,直到第一個(gè)線程用ReleaseMutex方法釋放該互斥體。
例子e7_4_3:使用Mutex類對象實(shí)現(xiàn)互斥。修改例子e7_4_1,為Form1類增加私有Mutex類變量:private Mutex mut。在Form1類構(gòu)造函數(shù)中建立Mutex類對象,在建立線程語句之前增加語句mut=new Mutex();修改例子e7_4_1中的兩個(gè)Fun1()和Fun2()方法如下:
public void Fun1()//在線程中執(zhí)行的方法,必須為公有void類型方法,不能有參數(shù)。
{?? int k,n;
for(k=0;k<4;k++)
{?? mut.WaitOne();//等待互斥體訪問權(quán)
n=num;// mut.WaitOne()和mut.ReleaseMutex()之間是互斥體
n++;//Mutex類對象mut的互斥體包含兩部分,函數(shù)Fun1和Fun2中的互斥體
Thread.Sleep(10);//有線程進(jìn)入一個(gè)互斥體,其它線程不能進(jìn)入任何一個(gè)互斥體
num=n;
mut.ReleaseMutex();//釋放互斥體訪問權(quán)
Thread.Sleep(50);
}
label1.Text=Convert.ToString(num);
}//退出該方法,線程結(jié)束
public void Fun2()
{?? int k,n;
for(k=0;k<4;k++)
{?? mut.WaitOne();
n=num;
n++;
Thread.Sleep(10);
num=n;
mut.ReleaseMutex();
Thread.Sleep(100);
}
label1.Text=Convert.ToString(num);
}
編譯,運(yùn)行,標(biāo)簽控件顯示8。如果有多個(gè)共享數(shù)據(jù)區(qū),可以定義多個(gè)Mutex類對象。
7.4.4? 用Monitor類實(shí)現(xiàn)互斥
也可以使用Monitor類保護(hù)共享資源不被多個(gè)線程或進(jìn)程同時(shí)訪問。Monitor類通過向單個(gè)線程授予對象鎖來控制對對象的訪問。只有擁有對象鎖的線程才能執(zhí)行臨界區(qū)的代碼,此時(shí)其它任何線程都不能獲取該對象鎖。只能使用Monitor類中的靜態(tài)方法,不能創(chuàng)建Monitor類的實(shí)例。Monitor類中的靜態(tài)方法主要有:
l 方法Enter:獲取參數(shù)指定對象的對象鎖。此方法放在臨界區(qū)的開頭。如其它線程已獲取對象鎖,則該線程將被阻塞,直到其它線程釋放對象鎖,才能獲取對象鎖。
l 方法Wait:釋放參數(shù)指定對象的對象鎖,以便允許其它被阻塞的線程獲取對象鎖。該線程進(jìn)入等待狀態(tài),等待狀態(tài)必須由其它線程用方法Pulse或PulseAll喚醒,使等待狀態(tài)線程變?yōu)榫途w狀態(tài)。
l 方法Pulse和PulseAll:向等待線程隊(duì)列中第一個(gè)或所有等待參數(shù)指定對象的對象鎖的線程發(fā)送信息,占用對象鎖的線程準(zhǔn)備釋放對象鎖。執(zhí)行方法Exit后將釋放對象鎖。
l 方法Exit:釋放參數(shù)指定對象的對象鎖。此操作還標(biāo)記受對象鎖保護(hù)的臨界區(qū)的結(jié)尾。
使用Monitor類實(shí)現(xiàn)互斥也很簡單,請讀者修改例子7_4_1,使用Monitor類實(shí)現(xiàn)互斥。Monitor類主要用來實(shí)現(xiàn)生產(chǎn)者和消費(fèi)者關(guān)系中的線程的同步,具體例子見下一節(jié)。
7.5?? 生產(chǎn)者線程和消費(fèi)者線程的同步
在生產(chǎn)者和消費(fèi)者關(guān)系中,生產(chǎn)者線程產(chǎn)生數(shù)據(jù),并把數(shù)據(jù)存到公共數(shù)據(jù)區(qū),消費(fèi)者線程使用數(shù)據(jù),從公共數(shù)據(jù)區(qū)取出數(shù)據(jù),并進(jìn)行分析。很顯然,如果公共數(shù)據(jù)區(qū)只能存一個(gè)數(shù)據(jù),那么在消費(fèi)者線程取出數(shù)據(jù)前,生產(chǎn)者線程不能放新數(shù)據(jù)到公共數(shù)據(jù)區(qū),否則消費(fèi)者線程將丟失數(shù)據(jù)。同樣,只有在生產(chǎn)者線程把數(shù)據(jù)已經(jīng)放到公共數(shù)據(jù)區(qū),消費(fèi)者線程才能取出數(shù)據(jù),如果新數(shù)據(jù)未放到公共數(shù)據(jù)區(qū),消費(fèi)者線程不能取數(shù)據(jù)。這些就是所謂的生產(chǎn)者和消費(fèi)者關(guān)系,必須要求生產(chǎn)者線程和消費(fèi)者線程同步。
7.5.1? 生產(chǎn)者線程和消費(fèi)者線程不同步可能發(fā)生錯(cuò)誤
例子e7_5_1:下邊的例子模擬生產(chǎn)者線程和消費(fèi)者線程不同步可能發(fā)生錯(cuò)誤。有一個(gè)公共變量,要求生產(chǎn)者線程順序放1到4到這個(gè)公共變量中,每放一個(gè)變量,消費(fèi)者線程取出這個(gè)數(shù)求和,最后把和顯示出來,顯然和應(yīng)為10。如不采取同步措施,和的結(jié)果不正確。
(1)?? 新建項(xiàng)目。在Form1.cs頭部增加語句:using System.Threading。
(2)?? 在窗體中放置一個(gè)標(biāo)簽控件,屬性Name=label1。
(3)?? 為Form1類定義2個(gè)線程類變量:Thread thread1,thread2。
(4)?? 為Form1類定義2個(gè)整形變量:int sum=0,x=-1。
(5)?? 為Form1類構(gòu)造函數(shù)增加語句如下:
thread1= new Thread(new ThreadStart(Fun1));
thread2= new Thread(new ThreadStart(Fun2));
thread1.Start();
thread2.Start();
(6)?? 為Form1類定義Fun1()和Fun2()方法如下:
public void Fun1()//生產(chǎn)數(shù)據(jù)
{?? int k,n;
for(k=1;k<5;k++)
{?? x=k;
Thread.Sleep(200);
}
}
public void Fun2()//消費(fèi)數(shù)據(jù)
{?? int k,n;
for(k=0;k<4;k++)
{?? sum+=x;
Thread.Sleep(100);
}
label1.Text=Convert.ToString(sum);
}
(7)?? 編譯,運(yùn)行,標(biāo)簽控件應(yīng)顯示10,實(shí)際運(yùn)行多次,顯示的數(shù)不為10。
7.5.2? 生產(chǎn)者線程和消費(fèi)者線程同步的實(shí)現(xiàn)
修改上例,為Form1類定義1個(gè)布爾變量:bool mark=false。其值為false,表示數(shù)據(jù)還未放到公共數(shù)據(jù)區(qū)(即x)中,生產(chǎn)者線程可以放數(shù)據(jù)到公共數(shù)據(jù)區(qū)中,由于沒有數(shù)據(jù),消費(fèi)線程不能取數(shù)據(jù),必須等待。mark=true,表示數(shù)據(jù)已放到公共數(shù)據(jù)區(qū)(即x)中,消費(fèi)線程還未取數(shù)據(jù),生產(chǎn)者線程不能再放數(shù)據(jù)到公共數(shù)據(jù)區(qū)中,必須等待。由于有了數(shù)據(jù),消費(fèi)線程可以取數(shù)據(jù)。修改Fun1()如下:
public void Fun1()//生產(chǎn)數(shù)據(jù)
{?? int k,n;
for(k=1;k<5;k++)
{?? Monitor.Enter(this);//這里this是Form1類對象,得到this的對象鎖
if(mark)//Monitor.Enter(this)和Monitor.Exit(this)是臨界區(qū)
Monitor.Wait(this);//如消費(fèi)者數(shù)據(jù)未取走,釋放對象鎖,生產(chǎn)者等待
Mark=!mark;
x=k;
Monitor.Pulse(this);//激活消費(fèi)者線程
Monitor.Exit(this);//釋放this的對象鎖
}
}
修改Fun2()如下:
public void Fun2()//消費(fèi)數(shù)據(jù)
{?? int k,n;
for(k=0;k<4;k++)
{?? Monitor.Enter(this);
if(!mark)
Monitor.Wait(this);//如果生產(chǎn)者未放數(shù)據(jù),消費(fèi)者等待
Mark=!mark;
sum+=x;
Monitor.Pulse(this);
Monitor.Exit(this);
}
label1.Text=Convert.ToString(sum);
}
編譯,運(yùn)行,標(biāo)簽控件應(yīng)顯示10。
轉(zhuǎn)載于:https://www.cnblogs.com/Aha-Best/p/10931701.html
總結(jié)
- 上一篇: 2019 课程设计个人报告
- 下一篇: BOM相关知识点