近期在重构老项目时发现有些地方用了AutoResetEvent,于是查了些资料学习整理。

线程同步介绍

即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态,实现线程同步的方法有很多,临界区对象就是其中一种。

同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。
“同”字从字面上容易理解为一起动作。其实不是,“同”字应是指协同、协助、互相配合。
如进程、线程同步,可理解为进程或线程A和B一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B运行;B依言执行,再将结果给A;A再继续操作。
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。
按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如Window API函数SendMessage,该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

为什么需要线程同步

在一般情况下,创建一个线程是不能提高程序的执行效率的,所以要创建多个线程。但是多个线程同时运行的时候可能调用线程函数,在多个线程同时对同一个内存地址进行写入,由于CPU时间调度上的问题,写入数据会被多次的覆盖,所以就要使线程同步。
多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。
 

C#线程同步

1、Lock关键字
lock是一种比较好用的简单的线程同步方式,它是通过为给定对象获取互斥锁来实现同步的。它可以保证当一个线程在关键代码段的时候,另一个线程不会进来,它只能等待,等到那个线程对象被释放,也就是说线程出了临界区。
注意:
  • lock的参数要求是对象,如果传入int,势必要发生装箱操作,这样每次lock的都将是一个新的不同的对象。
  • 最好避免使用public类型或不受程序控制的对象实例,因为这样很可能导致死锁。
  • 特别是不要使用字符串作为lock的参数,因为字符串被CLR“暂留”,就是说整个应用程序中给定的字符串都只有一个实例,因此更容易造成死锁现象。
  • 建议使用不被“暂留”的私有或受保护成员作为参数。
  • 其实某些类已经提供了专门用于被锁的成员,比如Array类型提供SyncRoot,许多其它集合类型也都提供了SyncRoot。
2、Monitor

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 类来实现的。

3、同步事件和等待句柄

  用lock和Monitor可以很好地起到线程同步的作用,但它们无法实现线程之间传递事件。如果要实现线程同步的同时,线程之间还要有交互,就要用到同步事件。

       同步事件是有两个状态(终止和非终止)的对象,它可以用来激活和挂起线程。这里的状态是事件的状态 而不是线程的状态。

  同步事件有两种:AutoResetEvent和 ManualResetEvent。它们之间唯一不同的地方就是在激活线程之后,状态是否自动由终止变为非终止。AutoResetEvent自动变为非终止,就是说一个AutoResetEvent只能激活一个线程。而ManualResetEvent要等到它的Reset方法被调用,状态才变为非终止,在这之前,ManualResetEvent可以激活任意多个线程。

  可以调用WaitOne、WaitAny或WaitAll来使 线程等待事件。它们之间的区别可以查看MSDN。当调用事件的 Set方法时,事件将变为终止状态,等待的线程被唤醒。

AutoResetEvent

同步事件对象AutoResetEvent 常常被用来在两个线程之间进行信号发送

两个线程共享相同的AutoResetEvent对象,一个线程A(可以是主线程)可以通过调用AutoResetEvent对象的WaitOne()方法进入等待状态,然后另外一个线程B通过调用AutoResetEvent对象的Set()方法取消 A线程等待的状态。

AutoResetEvent如何工作的

   在内存中保持着一个bool值(事件是否终止状态),如果bool值为False,则使线程阻塞,反之,如果bool值为True,则使线程退出阻塞。当我们创建AutoResetEvent对象的实例时,我们在函数构造中传递默认的bool值,

 AutoResetEvent形象说就像一个水龙头一样,当你关闭水龙头,状态就是关闭(终止状态),所有的水都在里面等待流出来,当水龙头打开,就发送一个事件说里面的水可以出来了,这时就是非终止状态,一般是用来同步访问资源.

AutoResetEvent主要方法

  1. AutoResetEvent(bool initialState):构造函数,用一个指示是否将初始状态设置为终止的布尔值初始化该类的新实例。 false:无信号,子线程的WaitOne方法不会被自动调用 true:有信号,子线程的WaitOne方法会被自动调用
  2. Reset ():将事件的状态设置为无信号,从而导致线程阻塞。如果该操作成功,则返回true;否则,返回false
  3. Set ():将事件的状态设置为已发出信号,从而允许一个或多个等待线程 继续进行。如果该操作成功,则返回true;否则,返回false。
  4. WaitOne(): 阻止当前线程,直到收到信号。
  5. WaitOne(TimeSpan, Boolean) :阻止当前线程,直到当前实例收到信号,使用 TimeSpan 度量时间间隔并指定是否在等待之前退出同步域。【对象等待 TimeSpan,然后继续执行】
 
实践:两个线程交替打印0~100的奇偶数。

   /// <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();
                }
            }
        }

    }

View Code

 
 

版权声明:本文为peterYong原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/peterYong/p/12162274.html