AutoResetEvent 類
AutoResetEvent類的工作方式與ManualResetEvent類似。它會等超時事件發生或者信號事件發生然后通知正在等待的線程。ManualResetEvent和AutoResetEvent之間最重要差別之一是AutoResetEvent在WaitOne()方法執行完會改變自身狀態。下面列表顯示了如何使用AutoResetEvent類:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace AutoReset { class Auto { [STAThread] static void Main() { AutoResetEvent aRE = new AutoResetEvent(true); Console.WriteLine("Before First WaitOne"); bool state = aRE.WaitOne(1000, true); Console.WriteLine("After First WaitOne " + state); state = aRE.WaitOne(5000, true); Console.WriteLine("After Second WaitOne " + state); Console.ReadLine(); } } }
AutoReset的輸出結果與上一篇的ManualReset例子一樣:
通過AutoReset例子,AutoResetEvent和ManualResetEvent的差別很清晰了。事件對象狀態在第一個WaitOne()函數由signaled變成non-signaled, 然后在第二個WaitOne()函數由non-signaled變成signaled. 結果是線程不會在第一個WaitOne()方法等待而不得不在第二個WaitOne()方法等待直到超時退出。
Mutex 類
Mutex, ManualResetEvent和AutoResetEvent類都繼承自WaitHandle類。它與Monitor非常類似除了它可以用於進程間同步而后者不可以。我們來看一個例子,WroxMutex.cs:
/************************************* /* Copyright (c) 2012 Daniel Dong * * Author:oDaniel Dong * Blog:o www.cnblogs.com/danielWise * Email:o guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace WroxMutex { class NETMutex { static Mutex myMutex; public static void Main() { myMutex = new Mutex(true, "WROX"); NETMutex nm = new NETMutex(); Thread t = new Thread(new ThreadStart(nm.Run)); t.Start(); Console.WriteLine("Thread Sleep for 5 sec"); Thread.Sleep(5000); Console.WriteLine("Thread Woke Up"); myMutex.ReleaseMutex(); Console.WriteLine("Before WaitOne"); myMutex.WaitOne(); Console.WriteLine("Lock owned by Main Thread"); Console.ReadLine(); } public void Run() { try { myMutex.WaitOne(); Console.WriteLine("Thread Sleep for 10 sec"); Thread.Sleep(10000); } catch (AbandonedMutexException ex) { Console.WriteLine("Exception on return from WaitOne." + "\r\n\tMessage: {0}", ex.Message); } finally { // Whether or not the exception was thrown, the current // thread owns the mutex, and must release it. // myMutex.ReleaseMutex(); } Console.WriteLine("End of Run() method"); } } }
WroxMutex輸出結果如下:
在WroxMutex中,我們使用一個布爾值和一個字符串來作為Mutex構造函數的參數,它們分別表示調用線程應該有Mutex的最初所有權以及Mutex的名字。我們然后創建一個線程用來調用Run()方法。Mutex仍然屬於主線程。在Run()方法中,線程t 不得不等待主線程釋放Mutex鎖。因此,線程t 會在Run()方法中對WaitOne()函數調用處等待。在睡眠5秒后,主線程釋放Mutex鎖。線程t獲得Mutex鎖的所有權並繼續睡眠。現在,Main()方法無法獲得Mutex 鎖直到線程t釋放它或者線程t退出。在這種情況下,線程t超時並退出,所以Mutex鎖又重新被主線程獲取。
Interlocked 類
Interlocked 可以用來對多線程共享訪問的一個整型變量進行同步訪問控制。這個操作在一個原子操作中進行。我們來看一個例子, WroxInterlocked.cs:
/************************************* /* copyright (c) 2012 daniel dong * * author:daniel dong * blog: www.cnblogs.com/danielwise * email: guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace WroxInterlocked { class WinterLocked { public ManualResetEvent a = new ManualResetEvent(false); private int i = 5; public void Run(object s) { Interlocked.Increment(ref i); Console.WriteLine("Thread ID = {0} Count = {1}", Thread.CurrentThread.GetHashCode(), i); } } public class MainApp { public static void Main() { ManualResetEvent mR = new ManualResetEvent(false); WinterLocked wL = new WinterLocked(); for (int i = 1; i <= 10; i++) { ThreadPool.QueueUserWorkItem(new WaitCallback(wL.Run), 1); } mR.WaitOne(10000, true); Console.ReadLine(); } } }
WroxInterlocked 的輸出結果如下:
WroxInterlocked 顯示了如何使用Interlocked類。我們在一個原子操作中增加全局變量i 的值。對應Increment()方法,也有一個Decrement()方法對變量值進行遞減。Exchange()方法也按照同樣行為更改作為ByRef 參數傳遞給它的兩個變量值。
靜態變量和靜態方法的同步
同步鎖對靜態變量和靜態方法的處理方法與實例變量和實例方法不同。靜態變量是類變量,而屬於一個對象的變量是對象變量或者實例變量。換句話說,一個類對象只有一個靜態變量和靜態方法實例而一個類對象的每個實例都可以有多個實例變量和實例方法。所以,如果你同步一個靜態變量或者一個靜態方法,那么鎖就會應用到整個類上(或者說類類對象的所有實例上,這也是為什么我們經常定義一個私有靜態對象作為鎖)。結果是其他對象都不被允許訪問類的靜態變量。
ThreadStaticAttribute 類
ThreadStaticAttribute 用於靜態變量,可以為每個訪問靜態變量的線程創建一個單獨的靜態變量拷貝,而不是在不同線程間共享靜態變量(默認行為)。這意味着應用了ThreadStaticAttribute 的靜態變量不會在訪問它的線程間共享。每個訪問它的線程都會擁有一個單獨的拷貝。如果一個線程修改了變量值,其他線程訪問同一個變量的時候不會看到改動后的值。這個行為與靜態變量的默認行為相悖。簡而言之,ThreadStaticAttribute 給了我們一個兩全其美的方案(靜態和實例)。
下面列表顯示了ThreadStaticAttribute的使用, WroxShared.cs:
/************************************* /* copyright (c) 2012 daniel dong * * author:daniel dong * blog: www.cnblogs.com/danielwise * email: guofoo@163.com * */ using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace WroxStatic { class ThreadStatic { [System.ThreadStaticAttribute()] public static int x = 1; public static int y = 1; public void Run() { for (int i = 1; i <= 10; i++) { Thread t2 = Thread.CurrentThread; x++; y++; Console.WriteLine("i = " + i +" ThreadID = " + t2.GetHashCode() +" x(static attribute) = " + x + " y = " + y); Thread.Sleep(1000); } } } public class MainApp { public static void Main() { ThreadStatic tS = new ThreadStatic(); Thread t1 = new Thread(new ThreadStart(tS.Run)); Thread t2 = new Thread(new ThreadStart(tS.Run)); t1.Start(); t2.Start(); Console.ReadLine(); } } }
WroxShared.cs 的輸出結果如下:
應用了ThreadStaticAttribute的一個靜態變量和一個實例變量的差別是靜態變量不需要生成一個對象實例來訪問它,而如果你不創建一個對象實例就訪問其實例變量,編譯時就會報錯。簡而言之,再一次的,應用了ThreadStaticAttribute的靜態變量不用生成新實例就可以訪問且每個訪問它的線程都有自己的拷貝,不會共享此靜態變量。
同步和性能
同步為我們的計算體驗帶來質的飛躍的同時也帶來了獲得同步鎖的時間開銷。結果是其性能總是比那么非線程安全的版本要低。由於多個線程可能會在同一時間訪問對象來獲得同步鎖,整體應用程序的性能可能在不經意間被影響。當開發人員在設計大型應用程序時必須要權衡好這類問題。最重要的部分是除非對吞吐量進行壓力測試否則這些線程性能問題不易被發現。在設計大規模的多線程應用程序時壓力測試是及其重要的。開發人員需要平衡這些因素:
為了安全,盡可能多的使用同步。但這會讓程序變得越來越慢,甚至比單線程版本還要慢。
為了性能,盡可能少的使用同步。
多線程設計就是這兩個因素之間不斷的平衡。
下一篇介紹 避免死鎖…