线程同步二
上一篇介绍了线程同步的两种方法,同步代码区和同步上下文。现在介绍第三种,手动同步。
三、手动同步
.net FrameWork 提供了常见的以下几种类可用于手动同步:AutoResetEvent、ManualResetEvent、Mutex、Interlocked。
1、AutoResetEvent类。AutoResetEvent类使线程处于等待状态,直到通过调用Set()方法某事件将AutoResetEvent对象置于有信号状态为止。在AutoResetEvent事件中,如果事件有信号别的等待线程可以获得信号执行线程中工作,当获得信号的线程执行后AutoResetEvent变为无信号状态,直到再次通过Set()方法使AutoResetEvent对象处理有信号状态。由上述可知,如果有信号别的等待线程就可以获得信号执行自己线程中的事件然后改变信号状态为无信号状态,但是如果AutoResetEvent对象信号一直存在,就说明无线程在等待信号。
示例:
using System; using System.Collections.Generic; using System.Text; using System.Threading; //注意此处添加引用。 namespace ThreadDemoCreateThead { class Program { static void Main(string[] args) { #region AutoResetEvent AutoResetEvent autoResetEvent = new AutoResetEvent(true); bool noSignal = autoResetEvent.WaitOne(2000, false); Console.WriteLine("有信号:" + noSignal); noSignal = autoResetEvent.WaitOne(2000, false); Console.WriteLine("有信号:" + noSignal); noSignal = autoResetEvent.WaitOne(2000, false); Console.WriteLine("是否还有信号:" + noSignal); autoResetEvent.Set(); noSignal = autoResetEvent.WaitOne(2000, false); Console.WriteLine("有信号:" + noSignal); noSignal = autoResetEvent.WaitOne(2000, false); Console.WriteLine("是否还有信号:" + noSignal); Console.ReadKey(); #endregion } } }
运行结果如下:
2、ManualResetEvent类。ManualResetEvent也用来使线程处于等待状态,直到通过调用Set()方法某事件将它置于有信号状态,注意ManualResetEvent对象的状态会一直保持有信号,直到Reset()方法将它显式设置为无信号状态,指定ManualResetEvent类构造函数参数为True时,使事件处于终止状态,使别的等待一个或多个等待线程继续。
示例:
using System; using System.Collections.Generic; using System.Text; using System.Threading; //注意此处添加引用。 namespace ThreadDemoCreateThead { class Program { static void Main(string[] args) { ManualResetEvent m1 = new ManualResetEvent(false); bool noSignal = m1.WaitOne(1000, false); Console.WriteLine("无信号:" + noSignal); m1.Set(); noSignal = m1.WaitOne(1000, false); Console.WriteLine("有信号:" + noSignal); noSignal = m1.WaitOne(1000, false); Console.WriteLine("是否还有信号:" + noSignal); m1.Reset(); noSignal = m1.WaitOne(1000, false); Console.WriteLine("Reset()后信号:"+noSignal); Console.ReadKey(); } } }
运行结果如下:
特别应当注意是ManualResetEvent类事件如果有信号,信号将一直存在,直到调用Reset()方法使事件无信号,此时信号无信号状态不会改变直到显式调用Set()使整体处于有信号状态。
3.Mutex类。可以使用Mutex对象提供对资源的独占访问。Mutex类比Monitor类使用更多系统资源,但是它可以跨应用程序域边界进行封送处理,可用于多个等待,并且可用于同步不同进程中的线程
示例:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; using System.Threading; namespace TcpClientDemo { static class Program { static Mutex myMutex =null; static void Main(string[] args) { myMutex = new Mutex(true, "Mutex"); Thread methodThread = new Thread(new ThreadStart(Run)); methodThread.Start(); Console.WriteLine("主线程运行操作5秒"); Thread.Sleep(5000); Console.WriteLine("主线程释放Mutex"); myMutex.ReleaseMutex(); Console.WriteLine("主线程等待再次获得Mutex"); myMutex.WaitOne(Timeout.Infinite); Console.WriteLine("主线程再次获得Mutex对象"); Console.WriteLine("主线程结束"); Console.ReadKey(); } /// <summary> /// 线程调用方法 /// </summary> static void Run() { Console.WriteLine("方法在等待获得Mutex"); myMutex.WaitOne(Timeout.Infinite); Console.WriteLine("方法获得Mutex操作权"); Thread.Sleep(5000); myMutex.ReleaseMutex(); Console.WriteLine("方法释放Mutex操作权"); Console.WriteLine("方法结束"); } } }
运行结果:
示例:主线程中使用Mutex构造函数初始化Mutex对象时,使用参数true指定主线程拥有Mutex对象使用权限,主纯种运行至ReleaseMutex()方法后,释放Mutex对象的所有权,此时方法线程获得Mutex对象所有权,方法线程得以执行,方法执行结束是主动释放Mutex对象的所有权,此后主线程再次获得Mutex对象所有权,继续执行主线程中剩下的方法。至此程序运行结束。
使用Mutex注意:1.Mutex分为本地Mutex和系统Mutex,本地Mutex主要用于本地程序线程间,系统Mutex对象可以用于进程间同步操作。2.mutex 具有线程关联;即 mutex 只能由拥有它的线程释放。如果线程释放不是它拥有的 mutex,则会在该线程中引ApplicationException。
4.Interlocked类。Interlocked类提供了若干方法,用于线程间共享变量的原子(注意是原子)操作。主要方法Add(),Read(),Exchange(),CompareExchange()等。下面示例展示Add()和Read()方法。
示例:
using System; using System.Collections.Generic; using System.Text; using System.Threading; //注意此处添加引用。 namespace ThreadDemoCreateThead { class Program { static int IncreateNum = 0; static object lockObj = new object(); static void Main(string[] args) { for (int i = 0; i < 5; i++) { ThreadPool.QueueUserWorkItem(AddNum); ThreadPool.QueueUserWorkItem(ReadNum); } Console.ReadKey(); } static void AddNum(object obj) { Interlocked.Add(ref IncreateNum, 10); Console.WriteLine("ThreadID:{0},Num:{1}", Thread.CurrentThread.GetHashCode(), IncreateNum); } static void ReadNum(object obj) { long tempNum=0; lock (lockObj) { tempNum=IncreateNum; } Interlocked.Read(ref tempNum); Console.WriteLine("ThreadID:{0},The Value of Num is :{1}", Thread.CurrentThread.GetHashCode(), tempNum); } } }
结果如下:
上例展示的是Add()方法和Read()方法,最常用的是Add()方法的简化版本Increment()相当于add(param1,1)Increment()相当于add( ref param1,1)即对变量进行加1操作;还有一个Decrement()方法和Increment()方法对应,即对变量进行减1的原子操作。
下面示例展示Exchange()方法和CompareExchange()方法。
示例:
using System; using System.Collections.Generic; using System.Text; using System.Threading; //注意此处添加引用。 namespace ThreadDemoCreateThead { class Program { static int srcValue = 10; static void Main(string[] args) { Console.WriteLine("srcValue:{0}", srcValue); int priValue = Interlocked.Exchange(ref srcValue, 30); Console.WriteLine("srcValue:{0},ChangedValue={1}", priValue, srcValue); Console.WriteLine("\r\n\r\n"); Console.WriteLine("***********************************"); Console.WriteLine("\r\n\r\n"); priValue = Interlocked.CompareExchange(ref srcValue, 40, 20); Console.WriteLine("srcValue:{0},ChangedValue={1}", priValue, srcValue); priValue = Interlocked.CompareExchange(ref srcValue, 40, 30); Console.WriteLine("srcValue:{0},ChangedValue={1}", priValue, srcValue); Console.ReadKey(); } } }
结果如下:
5.信号量。信号量类似于互斥锁,但是它可以授权多个线程同时访问同一个共享资源,因此在同步一个资源集合时,信号量显得非常有用。信号量通过使用一个计数器来控制对共享资源的访问。如果计数器大于0,就允许访问,如果计数器等于0就拒绝访问。
示例:
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace ThreadDemoCreateThead { class Program { static Semaphore se = new Semaphore(2, 2); static void Main(string[] args) { for (int i = 0; i < 5; i++) { Thread t = new Thread(new ThreadStart(Write)); t.Name = i.ToString(); t.Start(); } Console.ReadKey(); } static void Write() { se.WaitOne(); Console.WriteLine("Thread"+Thread.CurrentThread.Name+": 获得信号量"); Thread.Sleep(1000); Console.WriteLine("Thread" + Thread.CurrentThread.Name + ": 释放信号量"); se.Release(); } } }
运行结果:
信号量初始化时指定了初始信号量是2个,最大可用信号是2,因此在for循环是最多有两个线程在运行,别的线程等待运行线程调用Release()释放信号量获得信号量才能继续执行执行。
到此已经写完了线程同步要用到的重要类。下篇写线程同步简单的类计时器和线程池。