线程同步以及AutoResetEvent
近期在重构老项目时发现有些地方用了AutoResetEvent,于是查了些资料学习整理。
线程同步介绍
即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。
为什么需要线程同步
C#线程同步
- lock的参数要求是对象,如果传入int,势必要发生装箱操作,这样每次lock的都将是一个新的不同的对象。
- 最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。
- 特别是不要使用字符串作为lock的参数,因为字符串被CLR“暂留”,就是说整个应用程序中给定的字符串都只有一个实例,因此更容易造成死锁现象。
- 建议使用不被“暂留”的私有或受保护成员作为参数。
- 其实某些类已经提供了专门用于被锁的成员,比如Array类型提供SyncRoot,许多其它集合类型也都提供了SyncRoot。
Monitor类提供了与lock类似的功能,不过与lock不同的是,它能更好的控制同步块,当调用了Monitor的Enter(Object o)方法时,会获取o的独占权,直到调用Exit(Object o)方法时,才会释放对o的独占权,可以多次调用Enter(Object o)方法,只需要调用同样次数的Exit(Object o)方法即可,Monitor类同时提供了TryEnter(Object o,[int])的一个重载方法,该方法尝试获取o对象的独占权,当获取独占权失败时,将返回false。
但使用 lock 通常比直接使用 Monitor 更可取,一方面是因为 lock 更简洁,另一方面是因为 lock 确保了即使受保护的代码引发异常,也可以释放基础监视器。这是通过 finally 中调用Exit来实现的。事实上,lock 就是用 Monitor 类来实现的。
用lock和Monitor可以很好地起到线程同步的作用,但它们无法实现线程之间传递事件。如果要实现线程同步的同时,线程之间还要有交互,就要用到同步事件。
同步事件是有两个状态(终止和非终止)的对象,它可以用来激活和挂起线程。这里的状态是事件的状态 而不是线程的状态。
同步事件有两种:AutoResetEvent和 ManualResetEvent。它们之间唯一不同的地方就是在激活线程之后,状态是否自动由终止变为非终止。AutoResetEvent自动变为非终止,就是说一个AutoResetEvent只能激活一个线程。而ManualResetEvent要等到它的Reset方法被调用,状态才变为非终止,在这之前,ManualResetEvent可以激活任意多个线程。
可以调用WaitOne、WaitAny或WaitAll来使 线程等待事件。它们之间的区别可以查看MSDN。当调用事件的 Set方法时,事件将变为终止状态,等待的线程被唤醒。
AutoResetEvent
两个线程共享相同的AutoResetEvent对象,一个线程A(可以是主线程)可以通过调用AutoResetEvent对象的WaitOne()方法进入等待状态,然后另外一个线程B通过调用AutoResetEvent对象的Set()方法取消 A线程等待的状态。
AutoResetEvent如何工作的
在内存中保持着一个bool值(事件是否终止状态),如果bool值为False,则使线程阻塞,反之,如果bool值为True,则使线程退出阻塞。当我们创建AutoResetEvent对象的实例时,我们在函数构造中传递默认的bool值,
AutoResetEvent形象说就像一个水龙头一样,当你关闭水龙头,状态就是关闭(终止状态),所有的水都在里面等待流出来,当水龙头打开,就发送一个事件说里面的水可以出来了,这时就是非终止状态,一般是用来同步访问资源.。
- AutoResetEvent(bool initialState):构造函数,用一个指示是否将初始状态设置为终止的布尔值初始化该类的新实例。 false:无信号,子线程的WaitOne方法不会被自动调用 true:有信号,子线程的WaitOne方法会被自动调用
- Reset ():将事件的状态设置为无信号,从而导致线程阻塞。如果该操作成功,则返回true;否则,返回false
- Set ():将事件的状态设置为已发出信号,从而允许一个或多个等待线程 继续进行。如果该操作成功,则返回true;否则,返回false。
- WaitOne(): 阻止当前线程,直到收到信号。
- WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。【对象等待 TimeSpan,然后继续执行】
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <summary> /// 同步事件 测试 /// </summary> public class AutoResetEventTest { //若要将初始状态设置为终止,则为 true[有信号量];若要将初始状态设置为非终止【线程阻塞】,则为 false /// <summary> /// 线程同步事件对象 /// </summary> static AutoResetEvent oddResetEvent = new AutoResetEvent(false); static AutoResetEvent evenResetEvent = new AutoResetEvent(false); static int i = 0; public void Test() { //创建两个子线程:偶数线程和奇数线程 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++, num); if (i != 1) //第一次运行后,i=1,但是不需要evenResetEvent.Set(); evenResetEvent.Set(); oddResetEvent.WaitOne(); //当前子线程【偶数线程】阻塞,使子线程等待指定事件的发生 } else { Console.WriteLine("{0}:{1} {2} ", Thread.CurrentThread.Name, i++, num); //如果此时AutoResetEvent 为非终止状态,则线程会被阻止,并等待当前控制资源的线程通过调用 Set 来通知资源可用。否则不会被阻止 oddResetEvent.Set(); //使状态由非终止变为终止,重新激活子线程 evenResetEvent.WaitOne(); } } } }