本文主要來自一道面試題,由於之前對AutoResetEvent的概念比較模糊(即使已經使用過了)。面試題題目很簡潔:兩個線程交替打印0~100的奇偶數。你可以先動手試試,我主要是嘗試在一個方法里面完成這個任務。
注: Suspend,Resume來控制線程已經在.net framework2.0被淘汰了,原因就是掛起之后,但因為異常而沒有及時恢復,如果占用資源會導致死鎖。
注: Suspend,Resume來控制線程已經在.net framework2.0被淘汰了,原因就是掛起之后,但因為異常而沒有及時恢復,如果占用資源會導致死鎖。
AutoResetEvent概念
- AutoResetEvent對象用來進行線程同步操作,AutoResetEvent類繼承waitHandle類。waitOne()方法就繼承來自waitHandle類。
- AutoResetEvent對象有終止和非終止兩種狀態,終止狀態是線程繼續執行,非終止狀態使線程阻塞,可以調用set和reset方法使對象進入終止和非終止狀態。-》可以簡單理解如果AutoResetEvent對象是終止狀態,就像不管別人了,任你撒野去(waitOne()得到的都是撒野信號)
- AutoResetEvent顧名思義,其對象在調用一次set之后會自動調用一次reset,進入非終止狀態使調用了等待方法的線程進入阻塞狀態。-》可以簡單理解如果AutoResetEvent對象是非終止狀態,就開始管理起別人來了,此時waitOne()得到的信號都是呆在原地不動信號。
- waitHandle對象的waitone可以使當前線程進入阻塞狀態,等待一個信號。直到當前 waitHandle對象收到信號,才會繼續執行。
- set可以發送一個信號,允許一個調用waitone而等待線程繼續執行。 ManulResetEvent的set方法可以允許多個。但是要手動關閉,即調用reset();
- reset可以使因為調用waitone() 而等待線程都進入阻塞狀態。
AutoResetEvent主要方法及實踐
- AutoResetEvent(bool initialState):構造函數,用一個指示是否將初始狀態設置為終止的布爾值初始化該類的新實例。 false:無信號,子線程的WaitOne方法不會被自動調用 true:有信號,子線程的WaitOne方法會被自動調用
- Reset ():將事件狀態設置為非終止狀態,導致線程阻止;如果該操作成功,則返回true;否則,返回false。
- Set ():將事件狀態設置為終止狀態,允許一個或多個等待線程繼續;如果該操作成功,則返回true;否則,返回false。
- WaitOne(): 阻止當前線程,直到收到信號。
- WaitOne(TimeSpan, Boolean) :阻止當前線程,直到當前實例收到信號,使用 TimeSpan 度量時間間隔並指定是否在等待之前退出同步域。
有了上面的解釋,開始展示代碼(經過多次優化)
//若要將初始狀態設置為終止,則為 true;若要將初始狀態設置為非終止,則為 false
static AutoResetEvent oddResetEvent = new AutoResetEvent(false);
static AutoResetEvent evenResetEvent = new AutoResetEvent(false);
static int i = 0;
static void Main(string[] args)
{
//ThreadStart是個委托
Thread thread1 = new Thread(new ThreadStart(show));
thread1.Name = "偶數線程";
Thread thread2 = new Thread(new ThreadStart(show));
thread2.Name = "奇數線程";
thread1.Start();
Thread.Sleep(2); //保證偶數線程先運行。
thread2.Start();
Console.Read();
}
public static void show()
{
while (i <= 100)
{
int num = i % 2;
if (num == 0)
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, "evenResetEvent");
if(i!=1) evenResetEvent.Set();
oddResetEvent.WaitOne(); //當前線程阻塞
}
else
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, "oddResetEvent");
//如果此時AutoResetEvent 為非終止狀態,則線程會被阻止,並等待當前控制資源的線程通過調用 Set 來通知資源可用。否則不會被阻止
oddResetEvent.Set();
evenResetEvent.WaitOne();
}
}
}
結果如下圖所示:
注意點:
不要有一點點點點多余的evenResetEvent.Set(),他會讓后續的 evenResetEvent.WaitOne();失效.
第二種方法Semaphore
此外,我們利用信號量也可以實現,信號量是一種內核模式鎖,對性能要求比較高,特殊情況下才考慮使用,而且要避免在內核模式和用戶模式下頻繁相互切換線程。代碼如下:
private static readonly int MaxSize = 1;
private static int i = 0;
static Semaphore oddSemaphore = new Semaphore(0, MaxSize);
static Semaphore evenSemaphore = new Semaphore(0, MaxSize);
static void Main(string[] args)
{
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
//ThreadStart是個委托
Thread thread1 = new Thread(new ThreadStart(show));
thread1.Name = "偶數線程";
Thread thread2 = new Thread(new ThreadStart(show));
thread2.Name = "奇數線程";
thread1.Start();
thread2.Start();
thread1.Join();
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
Console.Read();
}
private static void show()
{
if(i==1) evenSemaphore.WaitOne();
while (i <= 100)
{
int num = i % 2;
if (num == 0)
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
evenSemaphore.Release();
oddSemaphore.WaitOne(); //當前線程阻塞
}
else
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
//釋放一個偶數信號空位出來;
oddSemaphore.Release();
evenSemaphore.WaitOne(); //當前線程阻塞
//此時已經消耗了一個奇數信號空位
}
}
}
第三種方法,約定每個線程只干自己的事
這種方法利用線程池本身就是隊列的方式,即先進先出。測試之后發現性能有下降,但是還是貼出來供參考。
static int threadCount = 2;
static int count = 0;
static object cursorLock = new object();
static void Main(string[] args)
{
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
Task[] arr = new Task[2];
for (int threadIndex = 0; threadIndex < threadCount; threadIndex++)
{
//這兩種方法都可以
arr[threadIndex] = Task.Factory.StartNew(PrintNum, threadIndex);
}
Task.WaitAll(arr);
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
Console.Read();
}
private static void PrintNum(object num)
{
bool isOk = false;
while (!isOk)
{
lock (cursorLock)
{
int index = count % 2;
if (count>100)
{
isOk = true;
}
else if (index == (int)num)
{
if (index == 0) Console.WriteLine("{0}:{1} {2} ", "偶數線程", Thread.CurrentThread.ManagedThreadId, count++);
else Console.WriteLine("{0}:{1} {2} ", "奇數線程", Thread.CurrentThread.ManagedThreadId, count++);
}
}
}
}
結果如下:
第四種方法 Mutex
private static int i = 0;
static Mutex mutex = new Mutex();
static void Main(string[] args)
{
System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
//ThreadStart是個委托
Thread thread1 = new Thread(new ParameterizedThreadStart(show));
thread1.Name = "偶數線程";
Thread thread2 = new Thread(new ParameterizedThreadStart(show));
thread2.Name = "奇數線程";
thread1.Start(0);
thread2.Start(1);
thread2.Join();
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed.TotalMilliseconds);
Console.Read();
}
/// <summary>
/// Mutex的釋放與鎖定 都只能在同一個線程中執行
/// </summary>
private static void show(object index)
{
while (i <= 100)
{
mutex.WaitOne();
int num = i % 2;
if (num == (int)index&&i<=100)
{
Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, Thread.CurrentThread.ManagedThreadId);
}
mutex.ReleaseMutex();
}
}
有關概念資料
https://www.cnblogs.com/michaelxu/archive/2008/09/20/1293716.html