淺解多線程(一)之線程入門起步
在多個線程運行的背景下,了解線程什麼時候結束,什麼時候停止是很有必要的。
案例:老和尚念經計時,2本經書,2個和尚念,一人一本,不能撕破,最短時間念完,問老和尚們念完經書最短需要多長時間。
分析:首先在開始念經的時候給計時,記為A,最後在記下慢和尚念完經書時的時間,記為B。求B-A
代碼:IsAlive屬性:標識此線程已啟動並且尚未正常終止或中止,則為 true,再念,沒念完,努力中;否則為 false,念完啦,歇著。
//和尚1,和尚2
public Thread td1, td2;
public void StarThread()
{
//開啟一個線程執行Hello方法,即和尚1念菠蘿菠蘿蜜
ThreadStart ts = new ThreadStart(Hello);
td1 = new Thread(ts);
td1.Start();
}
public void StarThread1()
{
//開啟一個線程執行Welcome方法,即和尚2念大金剛經
ThreadStart ts = new ThreadStart(Welcome);
td2 = new Thread(ts);
td2.Start();
}
public string sayh="", sayw="";
//菠蘿菠蘿蜜
public void Hello()
{
//念
sayh = "Hellow everyone ! ";
}
//大金剛經
public void Welcome()
{
//念
sayw = "Welcome to ShangHai ! ";
//偷懶10秒
Thread.Sleep(10000);
}
protected void btn_StarThread_Click(object sender, EventArgs e)
{
//記時開始,預備念
Response.Write("開始念的時間: "+DateTime.Now.ToString() + "</br>");
//和尚1就位
StarThread();
//和尚2就位
StarThread1();
int i = 0;
while (i == 0)
{
//判斷線程的IsAlive屬性
//IsAlive標識此線程已啟動並且尚未正常終止或中止,則為 true;否則為 false。
//如果兩個都為false說明,線程結束終止
if (!td1.IsAlive && !td2.IsAlive)
{
i++;
if (i == 1)
{
//念得內容,繞梁三尺。
Response.Write("我們年的內容: "+(sayh + " + " + sayw) + "</br>");
Response.Write("念完時的時間: "+DateTime.Now.ToString());
Response.End();
}
}
}
}

線程優先級區別於線程占有cpu時間的多少,當然優先級越高同等條件下占有的cpu時間越多。級別高的執行效率要高於級別低的。
優先級有5個級別:Lowest<BelowNormal<Normal<AboveNormal<Highest;默認為Normal。
案例:老和尚娶媳婦。佛祖說:你們3個和尚,清修刻苦,現特許你們娶媳婦啦,不過娶媳婦的只能是你們三個中間的一人。條件是我手中的經書誰能先念完,誰可以娶。
分析:和尚平時都很刻苦,各有特點,平時和尚1在lowest環境下念經,和尚2在normal環境下念經,和尚3在Highest環境下念經。
protected void btn_StarThread_Click(object sender, EventArgs e)
{
Write();
}
//i為和尚1念的頁數
//j為和尚2念的頁數
//k為和尚3念的頁數
//c為經書總頁數
int i=0,j=0,k=0,c=10000000;
//和尚1念經
public void Jsi()
{
while (i <= c)
{
i+=1;
}
}
//和尚2念經
public void Jsj()
{
while (j <= c)
{
j+=1;
}
}
//和尚3念經
public void Jsk()
{
while (k <= c)
{
k+=1;
}
}
public void Write()
{
//開啟線程計算i
ThreadStart sti = new ThreadStart(Jsi);
Thread tdi = new Thread(sti);
//設置線程優先級為Lowest。和尚1在Lowest環境下念經
tdi.Priority = ThreadPriority.Lowest;
//開啟線程計算j
ThreadStart stj = new ThreadStart(Jsj);
Thread tdj = new Thread(stj);
//設置線程優先級為Normal。和尚2在Normal環境下念經
tdj.Priority = ThreadPriority.Normal;
//開啟線程計算k
ThreadStart stk = new ThreadStart(Jsk);
Thread tdk = new Thread(stk);
//設置線程優先級為Highest。和尚3在Highest環境下念經
tdk.Priority = ThreadPriority.Highest;
//開始
tdj.Start();
tdk.Start();
tdi.Start();
int s = 0;
while (s==0)
{
if (k > c)
{
s++;
Response.Write("比賽結束,結果如下:</br></br>");
Response.Write("和尚1在Lowest環境下念經:" + i + "頁</br>和尚2在Normal環境下念經:" + j + "頁</br>和尚3在Highest環境下念經:" + k + "頁</br></br>");
Response.Write("佛祖又說:你念或者不念,蒼老師,就在那裡!");
Response.End();
}
}
}

為啦方便期間,從這以後,我要用控制台程序演示,操控線程。
如果,你的線程A中運行鎖內方法時候,需要去訪問一個暫不可用資源B,可能在B上需耗費很長的等待時間,那麼這時候你的線程A,將占用鎖內資源,阻塞其它線程訪問鎖定內容,造成性能損失。你該怎麼解決這樣子的問題呢?這樣,讓A暫時放棄鎖,停留在鎖中的,允許其它線程訪問鎖,而等B資源可用時,通知A讓他繼續鎖內的操作。是不是解決啦問題,這樣就用到啦這段中的Monitor類,提供的幾個方法:Wait(),Pulse(),PulseAll(),這幾個方法只能在當前鎖定中使用。
Wait():暫時中斷運行鎖定中線程操作,釋放鎖,時刻等待著通知復活。
Pulse():通知等待該鎖線程隊列中的第一個線程,此鎖可用。
PulseAll():通知所有鎖,此鎖可用。
案例:嵩山少林和尚開會。主持人和尚主持會議會不停的上舞台講話,方丈會出來宣布大會開始,弟子們開始討論峨眉山怎麼走。
分析:主持人一個線程,方丈一個線程,弟子們一個線程,主持人貫徹全場。
public class MutexSample
{
static void Main()
{
comm com = new comm();
com.dhThreads();
Console.ReadKey();
}
}
public class comm
{
//狀態值:0時主持人和尚說,1時方丈說,2時弟子們說,3結束。
int sayFla;
//主持人上台
int i = 0;
public void zcrSay()
{
lock (this)
{
string sayStr;
if (i == 0)
{
//讓方丈說話
sayFla = 1;
sayStr = Thread.CurrentThread.Name+"今晚,陽光明媚,多雲轉晴,方丈大師,程祥雲而來,傳揚峨眉一隅,情況如何,還請方丈閃亮登場。";
Console.WriteLine(sayStr);
i++;
//此時sayFla=1通知等待的方丈線程運行
Monitor.Pulse(this);
//暫時鎖定主持人,暫停到這裡,釋放this讓其它線程訪問
Monitor.Wait(this);
}
//被通知後,從上一個鎖定開始運行到這裡
if (i == 1)
{
//讓弟子說話
sayFla = 2;
sayStr = Thread.CurrentThread.Name + "看方丈那幸福的表情,徜徉肆恣,願走的跟他去吧。下面請弟子們各抒己見";
Console.WriteLine(sayStr);
i++;
//此時sayFla=12通知等待的弟子線程運行
Monitor.Pulse(this);
//暫時鎖定主持人,暫停到這裡,釋放this讓其它線程訪問
Monitor.Wait(this);
}
//被通知後,從上一個鎖定開始運行到這裡
if (i == 2)
{
sayFla = 3;
sayStr = Thread.CurrentThread.Name + "大會結束!方丈幸福!!蒼老師你在哪裡?!!放開那女孩 ...";
Console.WriteLine(sayStr);
i++;
Monitor.Wait(this);
}
}
}
//方丈上台
public void fzSay()
{
lock (this)
{
while (true)
{
if (sayFla != 1)
{
Monitor.Wait(this);
}
if (sayFla == 1)
{
Console.WriteLine(Thread.CurrentThread.Name + "藍藍的天空,綠綠的湖水,我看見,咿呀呀呀,看見一老尼,咿呀呀,在水一方。願意來的一起來,不願來的蒼老師給你們放寺裡。。咿呀呀,我走啦。。。");
//交給主持人
sayFla = 0;
//通知主持人線程,this可用
Monitor.Pulse(this);
}
}
}
}
//弟子上台
public void dzSay()
{
lock (this)
{
while (true)
{
if (sayFla != 2)
{
Monitor.Wait(this);
}
if (sayFla == 2)
{
Console.WriteLine(Thread.CurrentThread.Name + "果真如此的話,還是方丈大師自己去吧!! 祝福啊 .... ");
//交給主持人
sayFla = 0;
Monitor.Pulse(this);
}
}
}
}
public void dhThreads()
{
Thread zcrTd = new Thread(new ThreadStart(zcrSay));
Thread fzTd = new Thread(new ThreadStart(fzSay));
Thread dzTd = new Thread(new ThreadStart(dzSay));
zcrTd.Name = "主持人:";
fzTd.Name = "方丈:";
dzTd.Name = "弟子:";
zcrTd.Start();
fzTd.Start();
dzTd.Start();
}
}

多線程,共享一個資源,先後操作資源。Join()方法,暫停當前線程,直到指定線程運行完畢,才喚醒當前線程。如果沒有Join,多線程隨機讀取公用資源,沒有先後次序。
案例:兩個和尚念一本經書,老和尚年前半本書,小和尚念後半本書,小和尚調皮,非要先念,就給老和尚用迷魂藥啦。。
分析:一本書6頁,小和尚4-6,老和尚1-3,兩個和尚,兩個線程。
public class 連接線程Join
{
//小和尚
public static Thread litThread;
//老和尚
public static Thread oldThread;
//老和尚念經
static void oldRead()
{
//老和尚被小和尚下藥
litThread.Join(); //暫停oldThread線程,開始litThread,直到litThread線程結束,oldThread才繼續運行,如果不適用Join將小和尚一句,老和尚一句,隨即沒有規則的。
for (int i = 1; i <= 3; i++)
{
Console.WriteLine(i);
}
}
//小和尚念經
static void litRead()
{
for (int i = 4; i <= 6; i++)
{
Console.WriteLine(i);
}
}
static void Main(string[] args)
{
oldThread = new Thread(new ThreadStart(oldRead));
litThread = new Thread(new ThreadStart(litRead));
oldThread.Start();
// FristThread.Join(); //暫停oldThread線程,開始litThread,直到litThread線程結束,oldThread才繼續運行
litThread.Start();
Console.ReadKey();
}
}

互斥鎖是一個同步的互斥對象,適用於,一個共享資源,同時只能有一個線程能夠使用。
共享資源加互斥鎖,需要兩部走:1.WaitOne(),他將處於等待狀態知道可以獲取資源上的互斥鎖,獲取到後,阻塞主線程運行,直到釋放互斥鎖結束。2.ReleaseMutex(),釋放互斥鎖,是其它線程可以獲取該互斥鎖。
案例:和尚寫日記。最近寺廟香火不旺,為啦節約用水,方丈發話兩個和尚用一個本子寫日記。
分析:好比多個線程寫日志,同時只能有一個線程寫入日志文件。
public class 多線程互斥鎖Mutex
{
static void Main(string[] args)
{
IncThread ict = new IncThread("大和尚", 3);
DecThread dct = new DecThread("小和尚", 3);
Console.ReadKey();
}
}
class SharedRes
{
public static int count = 0;
//初始化互斥鎖,沒被獲取
public static Mutex mtx = new Mutex();
////初始化互斥鎖,被主調線程獲取
//public static Mutex mtx = new Mutex(true);
}
class IncThread
{
int num;
public Thread thrd;
public IncThread(string name ,int n)
{
thrd = new Thread(new ThreadStart(this.run));
thrd.Name = name;
num = n;
thrd.Start();
}
//寫日記,過程
void run()
{
Console.WriteLine(thrd.Name + " , 等待互斥鎖 。");
SharedRes.mtx.WaitOne();
Console.WriteLine(thrd.Name + " ,獲得互斥鎖 。");
do
{
Thread.Sleep(500);
SharedRes.count++;
Console.WriteLine("今天我 " + thrd.Name + " 比較強,這樣寫吧 :" + SharedRes.count);
num--;
}while(num>0);
Console.WriteLine(thrd.Name + " , 釋放互斥鎖 。");
SharedRes.mtx.ReleaseMutex();
}
}
class DecThread
{
int num;
public Thread thrd;
public DecThread(string name, int n)
{
thrd = new Thread(new ThreadStart(this.run));
thrd.Name = name;
num = n;
thrd.Start();
}
//寫日記,過程
void run()
{
Console.WriteLine(thrd.Name + ", 等待互斥鎖 。");
SharedRes.mtx.WaitOne();
Console.WriteLine(thrd.Name + " ,獲得互斥鎖 。");
do
{
Thread.Sleep(500);
SharedRes.count--;
Console.WriteLine("今天我 " + thrd.Name + " 比較衰,這樣寫吧 :" + SharedRes.count);
num--;
} while (num > 0);
Console.WriteLine(thrd.Name + " , 釋放互斥鎖 。");
SharedRes.mtx.ReleaseMutex();
}
}

類似於互斥鎖,只不過他可以指定多個線程來訪問,共享資源。在初始化信號量的同時,指定多少個線程可以訪問,假如允許2個線程訪問,而卻有3個線程等待訪問,那麼他將只允許2個訪問,一旦已訪問的2個線程中有一個訪問完成釋放信號量,那麼沒有訪問的線程立馬可以進入訪問。
案例:和尚抓雞,3個和尚抓雞,只有兩只雞,那麼雞圈管理員只允許2個和尚先進,抓到說三句話放下,出來,讓第三個和尚進去抓。
分析:三個線程,初始信號量允許2個線程訪問。
public class 信號量semaphore
{
static void Main(string[] args)
{
MyThread td1 = new MyThread("降龍");
MyThread td2 = new MyThread("伏虎");
MyThread td3 = new MyThread("如來");
td1.td.Start();
td2.td.Start();
td3.td.Start();
Console.ReadKey();
}
}
public class MyThread
{
//初始化,新號量,允許2個線程訪問,最大2也是2個。
public static Semaphore sem = new Semaphore(2,2);
public Thread td;
public MyThread(string name)
{
td = new Thread(new ThreadStart(this.Run));
td.Name = name;
}
//過程很美好
public void Run()
{
Console.WriteLine(td.Name+",等待一個信號量。");
sem.WaitOne();
Console.WriteLine(td.Name+",已經獲得新號量。");
Thread.Sleep(500);
//很有深意的三句話
char[] cr = { 'a','o','e'};
foreach (char v in cr)
{
Console.WriteLine(td.Name+",輸出v: "+v);
Thread.Sleep(300);
}
Console.WriteLine(td.Name+",釋放新號量。");
sem.Release();
}
}

結束,笑納,海涵。