C#线程同步的三类情景分析

 更新时间:2014年10月27日 08:59:28   投稿:shichen2014  
这篇文章主要介绍了C#线程同步的三类情景分析,较为详细生动的讲述了C#线程同步的三类情况,让大家对C#多线程程序设计有一个深入的了解,需要的朋友可以参考下

本文实例讲述了C#线程同步的三类情景,分享给大家供大家参考。具体分析如下:

C# 已经提供了我们几种非常好用的类库如 BackgroundWorker、Thread、Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序。

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

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

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

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

情景一:此茅坑有主了

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

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

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

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

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

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

情景一强调的是一对多的情形,而在情景二中,资源的数量并不唯一。相比于情景一,情景二侧重的是数量上的限制。而用于实现这一需求的类有: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#程序设计有所帮助。

相关文章

  • C#时间戳基本用法实例分析

    C#时间戳基本用法实例分析

    这篇文章主要介绍了C#时间戳基本用法,较为详细的讲述了时间戳的概念、生成方法与转换技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • mvc开启gzip压缩示例分享

    mvc开启gzip压缩示例分享

    这篇文章主要介绍了mvc开启gzip压缩示例,需要的朋友可以参考下
    2014-03-03
  • C#创建压缩文件的实现代码

    C#创建压缩文件的实现代码

    本篇文章主要介绍了C# 创建压缩文件的实现代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-05-05
  • c# Thread类的用法详解

    c# Thread类的用法详解

    这篇文章主要介绍了c# Thread类的用法的相关资料,帮助大家更好的理解和学习c#的相关知识,感兴趣的朋友可以了解下
    2020-11-11
  • C#泛型详解

    C#泛型详解

    本文详细讲解了C#中的泛型,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-03-03
  • 如何实现定时推送的具体方案

    如何实现定时推送的具体方案

    在工作当中遇到了一个需要定时向客户端推送新闻、文章等内容。小项目又用不了大框架,这个时候在网上搜了很久没有找到合适的解决方案,直到看到了一位大佬写的文章提供了一个非常不错的思路本篇文章也是受到他的启发实现了之后这里分享给大家
    2021-04-04
  • Unity UGUI的CanvasScaler画布缩放器组件介绍使用

    Unity UGUI的CanvasScaler画布缩放器组件介绍使用

    这篇文章主要为大家介绍了Unity UGUI的CanvasScaler画布缩放器组件介绍使用,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-07-07
  • C#6.0新语法示例详解

    C#6.0新语法示例详解

    这篇文章主要给大家介绍了关于C#6.0新语法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-12-12
  • C#使用Aspose.Cells控件读取Excel

    C#使用Aspose.Cells控件读取Excel

    本文介绍Aspose.Cells基础的用法,供大家参考。
    2016-03-03
  • C#解析json文件的实现代码

    C#解析json文件的实现代码

    最近需要用c#解析json文件,以前没用过这个,百度了一下找到了这篇文章感觉不错,特分享下
    2013-06-06

最新评论