多线程编程的复杂性在于识别多个线程可能同时访问的数据。同步可以防止数据的同时访问。
Monitor 可以看作是一个监视器,来阻止第二个线程进入一个受保护的代码段,直到第一个线程退出代码段。
Monitor主要使用的是Monitor.Enter() 和 Monitor.Exit(),但要保证Monitor.Exit()一定被调用,防止其长时间的阻止其他线程进入。
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TPL_Sync_Sample_Simple_Monitor { class Program { readonly static object _Sync = new object(); const int _Total = 1000; static long _Count = 0; static void Main(string[] args) { Task task = Task.Factory.StartNew(Decrement); for (int i = 0; i < _Total; i++) { bool lockToken = false; Monitor.Enter(_Sync, ref lockToken); try { _Count++; Console.WriteLine("Count ++ : {0}", _Count); } finally { if (lockToken) { Monitor.Exit(_Sync); } } } task.Wait(); Console.WriteLine("Count = {0}", _Count); Console.ReadKey(); } static void Decrement() { for (int i = 0; i < _Total; i++) { bool lockToken = false; Monitor.Enter(_Sync, ref lockToken); try { _Count--; Console.WriteLine("Count -- : {0}", _Count); } finally { if (lockToken) { Monitor.Exit(_Sync); } } } } } }
当使用Monitor时会频繁的使用try / finally 来保证一定会调用Monitor.Exit()。这里提供了一种 lock 的锁定方式。效果与Monitor一样。
using System; using System.Collections.Generic; using System.Text; using System.Threading; using System.Threading.Tasks; namespace TPL_Sync_Sample_Simple_Lock { class Program { readonly static object _Sync = new object(); const int _Total = 1000; static long _Count = 0; static void Main(string[] args) { Task task = Task.Factory.StartNew(Decrement); for (int i = 0; i < _Total; i++) { lock (_Sync) { _Count++; Console.WriteLine("++ {0} ", _Count); } } task.Wait(); Console.WriteLine("Count = {0}", _Count); Console.ReadKey(); } static void Decrement() { for (int i = 0; i < _Total; i++) { lock (_Sync) { _Count--; Console.WriteLine("-- {0} ", _Count); } } } } }
无论使用lock 还是 Monitor,都要小心的选择所要lock的对象。
例如程序中定义的对象
readonly static object _Sync = new object();
readonly 保证在 Monitor.Enter和Monitor.Exit中间值不会被改变。
private 保证类外的同步块不会访问它。
而且同步对象不能是值类型的。
volatile
编译器和CPU会对代码进行优化,使指令不按其编码顺序执行,或取消某些指令,这在多线程程序中会造成出乎意料的结果。volatile 关键字强迫所有的读写操作都在代码指定的位置执行。
// This example shows how a Mutex is used to synchronize access // to a protected resource. Unlike Monitor, Mutex can be used with // WaitHandle.WaitAll and WaitAny, and can be passed across // AppDomain boundaries. using System; using System.Threading; class Test { // Create a new Mutex. The creating thread does not own the // Mutex. private static Mutex mut = new Mutex(); private const int numIterations = 1; private const int numThreads = 3; static void Main() { // Create the threads that will use the protected resource. for (int i = 0; i < numThreads; i++) { Thread myThread = new Thread(new ThreadStart(MyThreadProc)); myThread.Name = String.Format("Thread{0}", i + 1); myThread.Start(); } // The main thread exits, but the application continues to // run until all foreground threads have exited. Console.ReadKey(); } private static void MyThreadProc() { for (int i = 0; i < numIterations; i++) { UseResource(); } } // This method represents a resource that must be synchronized // so that only one thread at a time can enter. private static void UseResource() { // Wait until it is safe to enter. mut.WaitOne(); Console.WriteLine("{0} has entered the protected area", Thread.CurrentThread.Name); // Place code to access non-reentrant resources here. // Simulate some work. Thread.Sleep(500); Console.WriteLine("{0} is leaving the protected area\r\n", Thread.CurrentThread.Name); // Release the Mutex. mut.ReleaseMutex(); } }
WaitHandle代码实例:
using System; using System.Threading; using System.Threading.Tasks; namespace TPL_Sync_Sample_WaitHandle_Task { class Program { static ManualResetEventSlim MainSignaledResetEvent = new ManualResetEventSlim(); static ManualResetEventSlim DoWorkSignaledResetEvent = new ManualResetEventSlim(); public static void DoWork() { Console.WriteLine("DoWork Start()."); DoWorkSignaledResetEvent.Set(); MainSignaledResetEvent.Wait(); Console.WriteLine("DoWork End()."); } static void Main(string[] args) { { Console.WriteLine("Main Start()."); Task task = Task.Factory.StartNew(DoWork); DoWorkSignaledResetEvent.Wait(); Console.WriteLine("Main Execute."); MainSignaledResetEvent.Set(); task.Wait(); Console.WriteLine("Main End()."); Console.ReadKey(); } } } }
semaphore代码实例:
using System; using System.Threading; public class Example { // A semaphore that simulates a limited resource pool. // private static Semaphore _pool; // A padding interval to make the output more orderly. private static int _padding; public static void Main() { // Create a semaphore that can satisfy up to three // concurrent requests. Use an initial count of zero, // so that the entire semaphore count is initially // owned by the main program thread. // _pool = new Semaphore(0, 3); // Create and start five numbered threads. // for (int i = 1; i <= 5; i++) { Thread t = new Thread(new ParameterizedThreadStart(Worker)); // Start the thread, passing the number. // t.Start(i); } // Wait for half a second, to allow all the // threads to start and to block on the semaphore. // Thread.Sleep(500); // The main thread starts out holding the entire // semaphore count. Calling Release(3) brings the // semaphore count back to its maximum value, and // allows the waiting threads to enter the semaphore, // up to three at a time. // Console.WriteLine("Main thread calls Release(3)."); _pool.Release(3); Console.WriteLine("Main thread exits."); Console.ReadKey(); } private static void Worker(object num) { // Each worker thread begins by requesting the // semaphore. Console.WriteLine("Thread {0} begins " + "and waits for the semaphore.", num); _pool.WaitOne(); // A padding interval to make the output more orderly. int padding = Interlocked.Add(ref _padding, 100); Console.WriteLine("Thread {0} enters the semaphore.", num); // The thread's "work" consists of sleeping for // about a second. Each thread "works" a little // longer, just to make the output more orderly. // Thread.Sleep(1000 + padding); Console.WriteLine("Thread {0} releases the semaphore.", num); Console.WriteLine("Thread {0} previous semaphore count: {1}", num, _pool.Release()); } }
using System; using System.Threading; namespace TPL_Sync_Sample_ThreadLocal { class Program { static ThreadLocal<int> _Count = new ThreadLocal<int>(()=>0); public static int Count { get { return _Count.Value; } set { _Count.Value = value; } } static void Main(string[] args) { Thread thread = new Thread(Decrement); thread.Start(); for (int i = 0; i < 1000; i++) { Count++; } thread.Join(); Console.WriteLine("Main Count = {0} ", Count); Console.ReadKey(); } static void Decrement() { for (int i = 0; i < 1000; i++) { Count--; } Console.WriteLine("Thread Count = {0}", Count); } } }
http://msdn.microsoft.com/zh-cn/library/vstudio/system.threading.timer.aspx
using System; using System.Threading; class TimerExample { static void Main() { // Create an event to signal the timeout count threshold in the // timer callback. AutoResetEvent autoEvent = new AutoResetEvent(false); StatusChecker statusChecker = new StatusChecker(10); // Create an inferred delegate that invokes methods for the timer. TimerCallback tcb = statusChecker.CheckStatus; // Create a timer that signals the delegate to invoke // CheckStatus after one second, and every 1/4 second // thereafter. Console.WriteLine("{0} Creating timer.\n", DateTime.Now.ToString("h:mm:ss.fff")); Timer stateTimer = new Timer(tcb, autoEvent, 1000, 250); // When autoEvent signals, change the period to every // 1/2 second. autoEvent.WaitOne(5000, false); stateTimer.Change(0, 500); Console.WriteLine("\nChanging period.\n"); // When autoEvent signals the second time, dispose of // the timer. autoEvent.WaitOne(5000, false); stateTimer.Dispose(); Console.WriteLine("\nDestroying timer."); Console.ReadKey(); } } class StatusChecker { private int invokeCount; private int maxCount; public StatusChecker(int count) { invokeCount = 0; maxCount = count; } // This method is called by the timer delegate. public void CheckStatus(Object stateInfo) { AutoResetEvent autoEvent = (AutoResetEvent)stateInfo; Console.WriteLine("{0} Checking status {1,2}.", DateTime.Now.ToString("h:mm:ss.fff"), (++invokeCount).ToString()); if (invokeCount == maxCount) { // Reset the counter and signal Main. invokeCount = 0; autoEvent.Set(); } } }