》,這次我們寫一個多線程的賽跑實例,內容很簡單:超人和蜘蛛俠賽跑,因為超人飛的比蜘蛛俠跳的快,為了公平,我們讓蜘蛛俠跑的長度小點,裁判負責宣布比賽的開始和結束。
[csharp]
class MultiThread
{
//定義兩個線程,分別為超人和蜘蛛俠
private static Thread SuperMan;
private static Thread SpiderMan;
//程序入口,比賽開始
static void Main(string[] args)
{
//初始化數據
InitData();
//裁判吹哨,開始賽跑
JudgeWork();
}
/// <summary>
/// 初始化超人和蜘蛛俠的線程和姓名
/// </summary>
private static void InitData()
{
SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SuperMan.Name = "SuperMan";
SpiderMan.Name = "SpiderMan";
}
/// <summary>
/// 裁判開始比賽,最後宣布勝者
/// </summary>
private static void JudgeWork()
{
Console.WriteLine("{0} PK {1}", SuperMan.Name, SpiderMan.Name);
Console.WriteLine("比賽即將開始,請各位做好准備!");
Console.WriteLine("預備!");
Console.Read();
//Superman起跑
Console.WriteLine("回車槍響,Superman開始起跑!");
Console.Beep(654, 1200);
SuperMan.Start(500);
//Monster起跑
Console.WriteLine("回車槍響,SpiderMan開始起跑!");
SpiderMan.Start(200);
SuperMan.Join();
SpiderMan.Join();
//宣布賽跑結果
Console.WriteLine("我宣布比賽結束");
//程序暫停12秒
Thread.Sleep(12000);
}
/// <summary>
/// 賽跑的過程
/// </summary>
/// <param name="obj">賽跑參數</param>
private static void RunnerWork(Object obj)
{
int length = Int32.Parse(obj.ToString());
Thread CurrentThread = Thread.CurrentThread;
string CurThreadName = CurrentThread.Name;
int speed;
//超人速度為20
if (CurThreadName == SuperMan.Name)
{
speed = 50;
}
//蜘蛛俠速度為20
else if (CurThreadName == SpiderMan.Name)
{
speed = 20;
}
//如果不可控線程進入,采用以下速度
else
{
speed = 1;
}
Console.WriteLine("{0},開始起跑…………", CurThreadName);
for (int count = speed; count <= length; count += speed)
{
Thread.Sleep(1000);
Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());
}
Console.WriteLine("{0},到達終點!了咧歡迎……", CurThreadName);
}
}
class MultiThread
{
//定義兩個線程,分別為超人和蜘蛛俠
private static Thread SuperMan;
private static Thread SpiderMan;
//程序入口,比賽開始
static void Main(string[] args)
{
//初始化數據
InitData();
//裁判吹哨,開始賽跑
JudgeWork();
}
/// <summary>
/// 初始化超人和蜘蛛俠的線程和姓名
/// </summary>
private static void InitData()
{
SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SuperMan.Name = "SuperMan";
SpiderMan.Name = "SpiderMan";
}
/// <summary>
/// 裁判開始比賽,最後宣布勝者
/// </summary>
private static void JudgeWork()
{
Console.WriteLine("{0} PK {1}", SuperMan.Name, SpiderMan.Name);
Console.WriteLine("比賽即將開始,請各位做好准備!");
Console.WriteLine("預備!");
Console.Read();
//Superman起跑
Console.WriteLine("回車槍響,Superman開始起跑!");
Console.Beep(654, 1200);
SuperMan.Start(500);
//Monster起跑
Console.WriteLine("回車槍響,SpiderMan開始起跑!");
SpiderMan.Start(200);
SuperMan.Join();
SpiderMan.Join();
//宣布賽跑結果
Console.WriteLine("我宣布比賽結束");
//程序暫停12秒
Thread.Sleep(12000);
}
/// <summary>
/// 賽跑的過程
/// </summary>
/// <param name="obj">賽跑參數</param>
private static void RunnerWork(Object obj)
{
int length = Int32.Parse(obj.ToString());
Thread CurrentThread = Thread.CurrentThread;
string CurThreadName = CurrentThread.Name;
int speed;
//超人速度為20
if (CurThreadName == SuperMan.Name)
{
speed = 50;
}
//蜘蛛俠速度為20
else if (CurThreadName == SpiderMan.Name)
{
speed = 20;
}
//如果不可控線程進入,采用以下速度
else
{
speed = 1;
}
Console.WriteLine("{0},開始起跑…………", CurThreadName);
for (int count = speed; count <= length; count += speed)
{
Thread.Sleep(1000);
Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());
}
Console.WriteLine("{0},到達終點!了咧歡迎……", CurThreadName);
}
} 運行結果:
比賽剛剛開始,裁判即宣布結束,這不符合常理。仔細分析可以發現,程序可控制的進程一共有三個,即裁判、超人和蜘蛛俠,三個進程相互獨立同時進行,所以裁判宣布比賽開始後即按照它的線程繼續宣布結束。
我們可以這樣:在裁判宣布比賽開始後,讓蜘蛛俠和超人的線程執行完畢再執行裁判進程:
[csharp]
//防止裁判的主進程先結束,讓超人和蜘蛛俠的進程先執行完畢
SuperMan.Join();
SpiderMan.Join();
Console.WriteLine("我宣布比賽結束");
//防止裁判的主進程先結束,讓超人和蜘蛛俠的進程先執行完畢
SuperMan.Join();
SpiderMan.Join();
Console.WriteLine("我宣布比賽結束"); 這次的執行結果為:
賽跑結束,裁判才宣布比賽結束,但是還有問題,裁判總得宣布誰跑贏了吧,台底下這麼多粉絲等著呢?這個我們可以用變量的方式保存署名,達到宣布誰為冠軍的功能。
為了展示同步異步讀寫問題,我們讓超人賽跑中去拯救世界,然後回來繼續比賽;先到達終點的人,自己花時間找粉筆,然後在黑板上署名,其他人看到黑板上有名字就不能再寫,裁判宣布署名的人為勝者。
[csharp]
class MultiThread3
{
//署名用的黑板
static string NameBoard = "";
//定義兩個線程,分別為超人和蜘蛛俠
private static Thread SuperMan;
private static Thread SpiderMan;
//程序入口,比賽開始
static void Main(string[] args)
{
//初始化數據
InitData();
//裁判吹哨,開始賽跑
JudgeWork();
}
/// <summary>
/// 初始化超人和蜘蛛俠的線程和姓名
/// </summary>
private static void InitData()
{
SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SuperMan.Name = "SuperMan";
SpiderMan.Name = "SpiderMan";
}
/// <summary>
/// 裁判開始比賽,最後宣布勝者
/// </summary>
private static void JudgeWork()
{
Console.WriteLine("{0} PK {1}", SuperMan.Name, SpiderMan.Name);
Console.WriteLine("比賽即將開始,請各位做好准備!");
Console.WriteLine("預備!");
Console.Read();
//Superman起跑
Console.WriteLine("回車槍響,SuperMan開始起跑!");
Console.Beep(654, 1200);
SuperMan.Start(500);
//Monster起跑
Console.WriteLine("回車槍響,SpiderMan開始起跑!");
SpiderMan.Start(300);
//防止裁判的主進程先結束,讓超人和蜘蛛俠的進程先執行完畢
SuperMan.Join();
SpiderMan.Join();
//宣布賽跑結果
AnnounceWinner();
//程序暫停12秒
Thread.Sleep(12000);
}
/// <summary>
/// 賽跑的過程
/// </summary>
/// <param name="obj">賽跑參數</param>
private static void RunnerWork(Object obj)
{
int length = Int32.Parse(obj.ToString());
Thread CurrentThread = Thread.CurrentThread;
string CurThreadName = CurrentThread.Name;
int speed;
//超人速度為20
if (CurThreadName == SuperMan.Name)
{
speed = 50;
}
//蜘蛛俠速度為20
else if (CurThreadName == SpiderMan.Name)
{
speed = 20;
}
//如果不可控線程進入,采用以下速度
else
{
speed = 1;
}
Console.WriteLine("{0},開始起跑…………", CurThreadName);
for (int count = speed; count <= length; count += speed)
{
Thread.Sleep(1000);
Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());
//超人跑到一半,去拯救世界
if (count == length / 2)
{
if (CurThreadName == SuperMan.Name)
{
Console.WriteLine("世界末日來臨,超人去拯救世界……");
string waitInfo = "..";
//超人拯救世界過程
for (int j = 0; j <= 10; j++)
{
Console.WriteLine("超人拯救世界中" + waitInfo);
waitInfo += "..";
Thread.Sleep(1000);
}
Console.WriteLine("超人去拯救世界歸來,繼續賽跑……");
}
}
}
Console.WriteLine("{0},到達終點!樂咧歡迎……", CurThreadName);
WriteName(CurThreadName);
}
/// <summary>
/// 跑到重點線後,選手自己在黑板上署名
/// </summary>
/// <param name="name">選手姓名</param>
private static void WriteName(string name)
{
//黑板上沒名字,才可以署自己的名字
if (NameBoard.Length == 0)
{
Console.WriteLine("{0}去找粉筆了……", name);
//找粉筆花費的時間
Thread.Sleep(9000);
Console.WriteLine("{0}拿著粉筆回來了,開始署名……", name);
NameBoard = name;
Console.WriteLine("{0}署完名後,開心的離開了……", name);
}
//黑板上有署名時不能再署名
else
{
Console.WriteLine("{0}發現已經署名,桑心的離開了……", name);
}
}
/// <summary>
/// 宣布比賽結果
/// </summary>
private static void AnnounceWinner()
{
Console.WriteLine("我是裁判,我宣布這次比賽的冠軍是{0}", NameBoard);
}
}
class MultiThread3
{
//署名用的黑板
static string NameBoard = "";
//定義兩個線程,分別為超人和蜘蛛俠
private static Thread SuperMan;
private static Thread SpiderMan;
//程序入口,比賽開始
static void Main(string[] args)
{
//初始化數據
InitData();
//裁判吹哨,開始賽跑
JudgeWork();
}
/// <summary>
/// 初始化超人和蜘蛛俠的線程和姓名
/// </summary>
private static void InitData()
{
SuperMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SpiderMan = new Thread(new ParameterizedThreadStart(RunnerWork));
SuperMan.Name = "SuperMan";
SpiderMan.Name = "SpiderMan";
}
/// <summary>
/// 裁判開始比賽,最後宣布勝者
/// </summary>
private static void JudgeWork()
{
Console.WriteLine("{0} PK {1}", SuperMan.Name, SpiderMan.Name);
Console.WriteLine("比賽即將開始,請各位做好准備!");
Console.WriteLine("預備!");
Console.Read();
//Superman起跑
Console.WriteLine("回車槍響,SuperMan開始起跑!");
Console.Beep(654, 1200);
SuperMan.Start(500);
//Monster起跑
Console.WriteLine("回車槍響,SpiderMan開始起跑!");
SpiderMan.Start(300);
//防止裁判的主進程先結束,讓超人和蜘蛛俠的進程先執行完畢
SuperMan.Join();
SpiderMan.Join();
//宣布賽跑結果
AnnounceWinner();
//程序暫停12秒
Thread.Sleep(12000);
}
/// <summary>
/// 賽跑的過程
/// </summary>
/// <param name="obj">賽跑參數</param>
private static void RunnerWork(Object obj)
{
int length = Int32.Parse(obj.ToString());
Thread CurrentThread = Thread.CurrentThread;
string CurThreadName = CurrentThread.Name;
int speed;
//超人速度為20
if (CurThreadName == SuperMan.Name)
{
speed = 50;
}
//蜘蛛俠速度為20
else if (CurThreadName == SpiderMan.Name)
{
speed = 20;
}
//如果不可控線程進入,采用以下速度
else
{
speed = 1;
}
Console.WriteLine("{0},開始起跑…………", CurThreadName);
for (int count = speed; count <= length; count += speed)
{
Thread.Sleep(1000);
Console.WriteLine("{0}……跑到了第{1}米", CurThreadName, count.ToString());
//超人跑到一半,去拯救世界
if (count == length / 2)
{
if (CurThreadName == SuperMan.Name)
{
Console.WriteLine("世界末日來臨,超人去拯救世界……");
string waitInfo = "..";
//超人拯救世界過程
for (int j = 0; j <= 10; j++)
{
Console.WriteLine("超人拯救世界中" + waitInfo);
waitInfo += "..";
Thread.Sleep(1000);
}
Console.WriteLine("超人去拯救世界歸來,繼續賽跑……");
}
}
}
Console.WriteLine("{0},到達終點!樂咧歡迎……", CurThreadName);
WriteName(CurThreadName);
}
/// <summary>
/// 跑到重點線後,選手自己在黑板上署名
/// </summary>
/// <param name="name">選手姓名</param>
private static void WriteName(string name)
{
//黑板上沒名字,才可以署自己的名字
if (NameBoard.Length == 0)
{
Console.WriteLine("{0}去找粉筆了……", name);
//找粉筆花費的時間
Thread.Sleep(9000);
Console.WriteLine("{0}拿著粉筆回來了,開始署名……", name);
NameBoard = name;
Console.WriteLine("{0}署完名後,開心的離開了……", name);
}
//黑板上有署名時不能再署名
else
{
Console.WriteLine("{0}發現已經署名,桑心的離開了……", name);
}
}
/// <summary>
/// 宣布比賽結果
/// </summary>
private static void AnnounceWinner()
{
Console.WriteLine("我是裁判,我宣布這次比賽的冠軍是{0}", NameBoard);
}
} 運行結果:
可以看到明明是SuperMan還在拯救地球時,SpiderMan已經到達終點,而裁判宣布的冠軍卻是SuperMan。仔細分析一下程序即可知道:雖然SpiderMan先到達終點,並且先發現黑板是空的,但是在SpiderMan尋找粉筆的過程中,SuperMan到達終點,並且也發現黑板是空的,於是兩人都寫上了自己的名字,但是因為後者的會覆蓋前者的,所以勝利者成了SuperMan,整個過程如下圖所示:
問題出現的原因在於,SpiderMan到達以後看到黑板,SuperMan仍然看到黑板,即這個黑板對於兩個人都是可寫的,後者會覆蓋前者的內容,這種方式為異步寫。
怎麼克服這個問題?可以使用Lock鎖住臨界區代碼,如下:
[csharp]
//定義一個對象類型的objLock
private static object objLock = new object();
/// <summary>
/// 跑到重點線後,選手自己在黑板上署名
/// </summary>
/// <param name="name">選手姓名</param>
private static void WriteName(string name)
{
//采用異步讀方式,篩選掉已經看到署名的線程,提高效率
//黑板上沒名字,才可以署自己的名字
if (NameBoard.Length == 0)
{
//因為上面為異步讀,所以可能多個線程可以進入到這一步
lock (objLock)
{
//同步讀方式
if (NameBoard.Length == 0)
{
Console.WriteLine("{0}去找粉筆了……", name);
//找粉筆花費的時間
Thread.Sleep(9000);
Console.WriteLine("{0}拿著粉筆回來了,開始署名……", name);
NameBoard = name;
Console.WriteLine("{0}署完名後,開心地離開了……", name);
}
//黑板上有署名時不能再署名
else
{
Console.WriteLine("{0}發現已經署名,桑心地離開了……", name);
}
}
}
}
//定義一個對象類型的objLock
private static object objLock = new object();
/// <summary>
/// 跑到重點線後,選手自己在黑板上署名
/// </summary>
/// <param name="name">選手姓名</param>
private static void WriteName(string name)
{
//采用異步讀方式,篩選掉已經看到署名的線程,提高效率
//黑板上沒名字,才可以署自己的名字
if (NameBoard.Length == 0)
{
//因為上面為異步讀,所以可能多個線程可以進入到這一步
lock (objLock)
{
//同步讀方式
if (NameBoard.Length == 0)
{
Console.WriteLine("{0}去找粉筆了……", name);
//找粉筆花費的時間
Thread.Sleep(9000);
Console.WriteLine("{0}拿著粉筆回來了,開始署名……", name);
NameBoard = name;
Console.WriteLine("{0}署完名後,開心地離開了……", name);
}
//黑板上有署名時不能再署名
else
{
Console.WriteLine("{0}發現已經署名,桑心地離開了……", name);
}
}
}
}
需要注意的是,鎖住的內容(非臨界區代碼)必須是共享型的引用型數據,因為如果是局部變量針對一個線程鎖不鎖對其它線程意義不大;采用引用數據類型,可以保證每個線程鎖住內容都指向同一個地址。
為了直觀顯示,沒有抽象出超人、蜘蛛俠和裁判的類,以上就是一個簡單的多線程應用實例,當然這是多線程的冰山一角,更多的還有待在以後開發中實踐,這次爭取在考試系統使用多線程優化抽題和判分等功能。