使用多線程技術能有效地協助你實現應用程式的更高效能和更優良的延展性。但在真正運用這項技術的時候務必小心。本文是對線程技術所牽扯的工具和技術問題系列文章的開篇。我首先對線程概念進行介紹,然後總結一些常用的構造,最後介紹它們的用法。
線程的兩面性
用Java語言編寫多線程程式並不難,這是好事也是壞事。微軟在開發C#時,他們把這種易用性的窘境全盤照搬到了整個新平台上。同時,C#相比Java具有更多的程式原語,但是Thread對象和同步監視器的基本Java原語從形式和功能上看都已足夠提供強大的線程編程能力了。因此,在決定為應用程式採用多線程技術之前務必小心。
為什麼不用多線程
首先得記住,在決定是否採用多線程技術時,除非你正在玩代碼,否則千萬別因為多線程編程夠“酷”而簡單地使用線程技術編程。多線程編程技術太時髦了,如果你不小心點你的老闆遲早也會著迷,那時你就死定了。其次,不要因為讓程式運行得更快而輕易採用多線程,除非你真的能證明單線程實現確實慢得可以。最後,在冒昧地一頭紮進多線程機制之前,先回憶下微軟所提供的一種公寓(apartment)模型,也就是把對象寫成單線程構造而運行在多線程環境下。所以,說來說去,你並不一定非要採用多線程編碼。不過,公寓模型是另外一個話題了。
如果做得不對,多線程編程勢必會開啟“潘朵拉的盒子”(意思是說惹出無數的麻煩)。重複性不明顯、產生程式垃圾、記數器沒有正確增值等等。你的應用程式還可能突然掛起。例如,資料庫連接這類資源就可能出人意料地關閉或者變得過載。進階開發人員所面臨的一個大麻煩就是解決線程問題。這些大問題不花點時間休想解決,而且它們對產品交貨日期以及產品可靠性產生了嚴重的負面影響。
為什麼要用多線程
如果你的應用程式需要採取以下的操作,那麼你盡可在編程的時候考慮多線程機制:
連續的操作,需要花費忍無可忍的過長時間才可能完成
並行計算
為了等待網路、檔案系統、使用者或其他I/O響應而耗費大量的執行時間
所以說,在動手之前,先保證自己的應用程式中是否出現了以上3種情形。
如果你的代碼運行得足夠快,但是你認為你能讓它運行得更快(假設你確實有這本事),我勸你最好不要接受這種誘惑。如果你不能肯定程式的計算操作並行性(例如針對同一資料表的並發資料庫更改——當你的資料庫達到了資料表級鎖定的情況下),那麼再想想其他法子吧。還有,如果你不知道應用程式是否因為等待輸入或輸出而花費了過多的時間,那麼請首先搞清楚真正耗費時間的情況再說。實際上,啟動3個線程以百萬分之一的步長計算圓周率所消耗的時間就比同一線程重複計算3次要長得多。為什麼會出現這種失敗的情形呢?原因就在於,雖然第2條並行計算確實可用,但設計者卻恰恰忽略了以上第3個標準:並行計算可以用到的一次計算期間卻沒有空閑周期。
假如你在為一台裝備了多個處理器的並行電腦編寫程式,則以上規則在這種情況下例外,你可以通過適當的並行操作設計而令軟體效能大大獲益——哪怕每一操作都對CPU時間極其貪婪。
基本的線程管理工具
剛才我已經為多線程編程提出了相當程度的警告,同時還為何時使用或者不使用多線程提出了建議,接下來我對多線程編程所能利用的某些工具進行闡述。
Thread對象
.NET庫提供了一種名為System.Threading.Thread的對象,這種對象代表了單一線程。你可以啟動線程、在當前線程繼續啟動並執行情況下設法完成線程的任務。這對那些需要列印文檔或者儲存大型檔案但希望獲得使用者確認請求並給使用者返回控制的應用程式來說協助實在太大了。我們通過程式清單A示範了這一機制。
線程編程主要包括:線程的建立、掛起、喚醒、終止等操作
具體測試代碼如下所示:
using System;<br />using System.Collections.Generic;<br />using System.Linq;<br />using System.Text;<br />using System.Threading;</p><p>namespace ThreadTest<br />{<br /> class Program<br /> {<br /> static void Main(string[] args)<br /> {<br /> //Thread thread = new Thread(new ThreadStart(HelloWorld));<br /> //thread.Start();//開始一個線程</p><p> //thread.Priority = ThreadPriority.Highest;</p><p> //if (thread.IsAlive)//判斷線程狀態<br /> //{<br /> // Console.WriteLine("Thread is Alive");<br /> // Console.WriteLine(thread.ThreadState.ToString());<br /> //}</p><p> //if (thread.ThreadState == ThreadState.Running)//掛起一個線程<br /> //{<br /> // Thread.Sleep(1000);//暫停線程<br /> // Console.WriteLine("Thread is Suspend");<br /> // Console.WriteLine(thread.ThreadState.ToString());<br /> //}</p><p> //if (thread.ThreadState == ThreadState.Suspended)//喚醒一個線程<br /> //{<br /> // thread.Resume();<br /> // Console.WriteLine("Thread is Resume");<br /> // Console.WriteLine(thread.ThreadState.ToString());<br /> //} </p><p> //thread.Abort();</p><p> //if (!thread.IsAlive)<br /> //{<br /> // Console.WriteLine("Thread is Over");<br /> //}<br /> Thread thread1 = new Thread(new ThreadStart(HelloWorld));<br /> Thread thread2 = new Thread(new ThreadStart(HellodotNet));<br /> thread1.Start();<br /> thread2.Start();<br /> Console.Read();<br /> }</p><p> protected static void HelloWorld()<br /> {<br /> for (int i = 1; i < 3; i++)<br /> {<br /> Console.WriteLine("Hello World!");<br /> Thread.Sleep(1000);<br /> }<br /> }</p><p> protected static void HellodotNet()<br /> {<br /> for (int i = 1; i < 3; i++)<br /> {<br /> Console.WriteLine("Hello dotNet!");<br /> Thread.Sleep(300);<br /> }<br /> }<br /> }<br />}<br />
另外一個代碼如下所示:
using System;<br />using System.Threading;</p><p>// Simple threading scenario: Start a static method running<br />// on a second thread.<br />public class ThreadExample<br />{<br /> // The ThreadProc method is called when the thread starts.<br /> // It loops ten times, writing to the console and yielding<br /> // the rest of its time slice each time, and then ends.<br /> public static void ThreadProc()<br /> {<br /> for (int i = 0; i < 10; i++)<br /> {<br /> Console.WriteLine("ThreadProc: {0}", i);<br /> // Yield the rest of the time slice.<br /> Thread.Sleep(0);<br /> }<br /> }</p><p> public static void Main()<br /> {<br /> Console.WriteLine("Main thread: Start a second thread.");<br /> // The constructor for the Thread class requires a ThreadStart<br /> // delegate that represents the method to be executed on the<br /> // thread. C# simplifies the creation of this delegate.<br /> Thread t = new Thread(new ThreadStart(ThreadProc));<br /> // Start ThreadProc. On a uniprocessor, the thread does not get<br /> // any processor time until the main thread yields. Uncomment<br /> // the Thread.Sleep that follows t.Start() to see the difference.<br /> t.Start();<br /> //Thread.Sleep(0); </p><p> for (int i = 0; i < 4; i++)<br /> {<br /> Console.WriteLine("Main thread: Do some work.");<br /> Thread.Sleep(0);<br /> }</p><p> Console.WriteLine("Main thread: Call Join(), to wait until ThreadProc ends.");<br /> t.Join();<br /> Console.WriteLine("Main thread: ThreadProc.Join has returned. Press Enter to end program.");<br /> Console.ReadLine();<br /> }<br />}<br />