C# 线程同步的三类情景

  C# 已经提供了咱们几种很是好用的类库如 BackgroundWorker、Thread、Task等,借助它们,咱们就可以分分钟编写出一个多线程的应用程序。html

  好比这样一个需求:有一个 Winform 窗体,点击按钮后,会将窗体中的数据导出到一个 output.pdf 文件中。原先的代码没有采用多线程技术,因此当点击按钮后,整个窗体就变成无响应了。为了解决这个问题,可使用 Task.Run(()=>{...导出文件的代码}); 数据库

  上面的代码看似简单,却隐藏着种种危机。若是在导出的期间,窗体的数据被修改了,那会怎么样?若是多个窗体同时导出到同一个文件,又会怎么样?多线程

  在看完本系列后,你就会清楚了。并发

 

  有点了解的朋友都知道线程同步有多种手段,什么 mutex、moniter、seamphore、event 等等,我把它们归为三类,对应三种须要线程同步的情景。post

 

情景一:此茅坑有主了

   当一个资源同时被多个线程访问时,有可能会形成资源冲突(尤为是在存在多个写线程的时候)的情景。遇到这种状况,在 C# 中,咱们可使用 Interlocked、lock、Moniter、SpinLock、ReadWriteLockSlim、Mutex 来处理问题。spa

关于不一样方案间的区别,请猛击这里线程

 

  什么状况下会被认为是情景一?设计

  当你设计的类中出现静态变量、IO操做时,就会遇到情景一。由于这些资源是由多个对象共享的,不一样的线程很同时去访问这些资源时,就可能会出现争用。code

  当一个类被设计成单例,且包含实例变量时,也会遇到情景一。由于实例变量属于这个单例,当多个线程操纵此单例时,该变量可能会被争用。orm

  当一个类中的方法调用线程操做某个实例变量时,也会遇到情景一。

 

情景二:数量有限,先到先得

   情景一强调的是一对多的情形,而在情景二中,资源的数量并不惟一。相比于情景一,情景二侧重的是数量上的限制。而用于实现这一需求的类有:Semaphore、SemaphoreSlim。

  关于不一样方案间的区别,请猛击这里

 

  什么状况下会被认为是情景二?

  当所操做的公共资源存在并发数限制的时候(如数据库链接、IIS链接数限制等),就被认为是情景二。

 

情景三:我让你动,你才能动!

   情景三关注的是线程执行过程当中的前后顺序,而用于保证这种前后顺序的方式就是经过线程通讯的方式:ManualResetEventSlim、ManualResetEvent、AutoResetEvent。

  关于不一样方案间的区别,请猛击这里

 

  什么状况下会被认为是情景三?

  当两个线程所处理的事情有前后的依赖时,好比线程二的执行过程依赖线程一的执行结果,那就认为是情景三。

 

不限使用情景

  上面的各类方案并非绝对只限于某一场景,好比 AutoResetEvent 便可以用于情景三,也能够用于情景一。可是,杀鸡焉用牛刀,虽然使用 AutoResetEvent 可以实现情景一的需求,可是用不了 AutoResetEvent 的线程通讯能力,同时又会有一些额外的限制(每一个线程必须保证 wait 和 set 的成对使用,不然一个线程在锁定资源后就可能被另外一个线程解锁)。 

    lock (m)
    {
        //....
    }
     
    //等价于以下方式
    autoResetEvent.WaitOne();
    //....
    autoResetEvent.Set();


  也有朋友说,能够用情景一中的 lock 方案来实现情景三的需求。 

    AutoResetEvent autoReset = new AutoResetEvent(false);
    private void button1_Click(object sender, EventArgs e)
    {      
        Task.Run(() =>
        {
            autoReset.WaitOne();
            Console.WriteLine("步骤二");
        });
 
        Thread.Sleep(1000);//故意延迟从而保证第二个线程是在第一个线程以后才执行
        Task.Run(() =>
        {
            Console.WriteLine("步骤一");
            autoReset.Set();
        });
    }

  上面这个例子最终输出的结果可想而知。此实例说明,无论线程实际的执行顺序如何,AutoResetEvent 都能很容易的保证两个线程的执行顺序。

 

  若是用 lock 呢? 

    private void button1_Click(object sender, EventArgs e)
    {
        Task.Run(() =>
        {
            lock (s)
            {
                Console.WriteLine("步骤一");
            }
        });
 
        Thread.Sleep(1000);//必须人为确保步骤二的线程要在步骤一的线程以后执行
        Task.Run(() =>
        {
            lock (s)
            {
                Console.WriteLine("步骤二");
            }
        });
    }

  虽然能实现,可是须要花费额外的代码去人为保证两个线程的执行顺序。

 

  如何在这么多方案中肯定最终所使用的,须要你能对项目的各类情景进行分析,根据实际情景选择对应的方案,而不至于大材小用。

 

总 结

   经过本系列文章的介绍,但愿让你们能对多线程中可能碰到的情景有一个概念,不至于在面临多线程的时候手忙脚乱。

 

  本文来自《C# 基础回顾: 线程同步的三类情景

相关文章
相关标签/搜索