c#多线程编程-必知必会

dotNET跨平台 dotNET跨平台     2023-03-17     492

关键词:

发现问题的能力,运用技术解决问题的能力,是一个技术人成长的关键

@图片故事:洋姜的花,拍摄于2022年7月23日,地点:北京奥林匹克森林公园 ,摄影师:刘先生

概要:使用C#发起多线程任务十分简单,本文旨在汇总多线程编程的注意事项,重点不在于如何发起多线程,主要内容如下:

  1. 控制线程并发数量

  2. 界定共享资源

  3. 加锁并控制锁范围

  4. 子线程异常处理

  5. 未完成任务取消

    希望对小伙伴儿们有所帮助

01


控制线程并发数量

多线程以多任务并行的方式,加快业务处理速度,但如果线程数量超出了系统的承载能力,反倒会造成系统整体性能下降,如何合理地控制线程并发数量,是多线程开发的关键。

推荐采用信号量机制,可以在线程总数未知的情况下,有效地控制并发线程数量,并且以瀑布流的形式,连续执行后续线程,逻辑清晰可控,执行性能高效。

基础代码逻辑如下:

//semaphoreCount是设定的可并行运行的最大线程数量
//taskCount是需要发起的线程的数量
using (Semaphore semaphore = new Semaphore(semaphoreCount, semaphoreCount))

    var woker = new Worker();
    Task[] tasks = new Task[taskCount];
    for (int step = 0; step < taskCount; step++)   
            
        //获取一个信号量,如果所有信号量都已使用,则等待直到一个被释放        
        semaphore.WaitOne();        
        //获得信号量之后,才能发起子线程        
        tasks[step] = Task.Factory.StartNew((data) =>  woker.Work(data); , innerData)
                                  .ContinueWith((task) =>                                  
                                                                        
                                      //线程完成,释放信号量                                      
                                      semaphore.Release();                                  
                                  );    
         
     //...

简单来说,是由于分时操作系统,多任务之间存在线程上下文切换,有兴趣的同学可以尝试一下,一次性启动2000个以上线程,查看计算机的资源耗用情况,以便有更真切的体会。

02


界定共享资源

线程共享资源,一类是业务本身需要多个子线程共同处理的资源,另一类是从性能角度考虑,需要被多个子线程共享的资源。

以数据查询为例,数据库连接是一种昂贵的资源,如果每个子线程单独创建数据库连接,必然会造成浪费,多个线程共用一个数据库连接是更合理的选择,因此,数据库连接便是共享资源。

有兴趣的同学可以测试一下,同时启动50个以上线程,如果每个线程创建一个数据库连接,会造成数据库短时间内无法创建足够连接而报错。

03


加锁并控制锁范围

对共享资源进行访问时,需要加锁保护,防止并发错误。

对于业务本身处理的共享资源,加锁主要是防止数据处理错误;对于集合类型的共享资源,建议首选System.Collections.Concurrent 命名空间下的集合类型,以达到线程安全的目的;对于如数据库连接之类的资源,加锁是为了防止程序异常,如数据库连接、HttpClient对象,在一个请求处理完之前,是不能被其他线程访问的,因此需要加锁,确保串行访问是必须的。

对于锁对象,推荐的写法如下,至于是不是要加static ,要看具体业务场景,静态变量的作用域是整个应用程序,如果有两个以上请求同时到达,那么在访问到加锁代码块时,请求也是串行执行的,普通变量的作用域是当前对象,锁范围也是在当前对象内,请求间相互不影响。

readonly object locker = new object();

04


子线程异常处理

概括成一句话是:在明确异常处理要做什么的情况下,才进行异常处理,否则,让异常抛出,交由外层程序处理即可。参考我上一篇文章:异常处理,究竟是处理什么

多线程下异常处理的不同之处在于:子线程内的异常,不会直接抛出到主线程,而是保存在了Task对象的Exception属性中。因此,需要开发小伙伴判断线程状态,进行异常处理。

基础代码逻辑如下:

Task.Factory.StartNew((data) =>  woker.Work(data); , innerData)            
            .ContinueWith((task) =>            
                            
              //判断线程处理状态,如果执行失败,则抛出异常                
              if (task.Status == TaskStatus.Faulted)                
                                  
                  throw task.Exception;                
                    
            );

05


未完成任务取消

当某个子线程发生异常之后,取消后续相关线程的执行,符合绝大多数业务逻辑。

取消线程操作需要用到 CancellationTokenSource 类,线程启动时,注册“取消凭证(Token)”,当某个子线程发生异常后,调用CancellationTokenSource的Cancel()方法,通知相关线程取消操作。以后会写一篇CancellationToken的详细介绍。

基础代码逻辑如下:

//声明 CancellationTokenSource
using (CancellationTokenSource cancellation = new CancellationTokenSource())

    Task[] tasks = new Task[taskCount];
    for (int step = 0; step < steps; step++)
    
        semaphore.WaitOne();
        //注册cancellation.Token
        tasks[step] = Task.Factory.StartNew((data) =>  woker.Work(data); , innerData, cancellation.Token)
                                .ContinueWith((task) =>
                                
                                    if (task.Status == TaskStatus.Faulted)
                                    
                                        //通知取消任务
                                        cancellation.Cancel(true);
                                        throw task.Exception;
                                    
                                    semaphore.Release();
                                );
    

有多线程开发经历的小伙伴,可以看一下自己的代码,是否有对以上几点的处理。以上内容均来自于我个人的经验总结,如有疏漏,欢迎小伙伴补充指正。

最后,说一下对于多线程的认识,了解二次元的小伙伴应该知道一个词:“结界”,线程与结界有很多相似之处,一个子线程就相当于一个结界,结界内外虽处于同一空间,但却属于不同的世界,结界阻断了结界内外的联系,但又可以相互作用,更多相似处,小伙伴们自己体会。


您的反馈是我坚持的动力,欢迎点赞,转发,关注

并发编程:线程与多线程必知必会(代码片段)

1线程与多线程线程是什么?线程(Thread)是一个对象(Object)。用来干什么?Java线程(也称JVM线程)是Java进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。Java程序采用多线... 查看详情

2018年线程与多线程面试必知必会内容(代码片段)

本文目录线程与多线程线程的运行与创建线程的状态1线程与多线程线程是什么?线程(Thread)是一个对象(Object)。用来干什么?Java线程(也称JVM线程)是Java进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thr... 查看详情

多线程必知必会的知识点

1)现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?这个线程问题通常会在第一轮或电话面试阶段被问到,目的是检测你对”join”方法是否熟悉。这个多线程问题比较简单,可以用join方法实现。2)... 查看详情

android开发必知必会:java线程池(代码片段)

...编程技巧,我们日常工作中常见的有数据库连接池、线程池、对象池等,它们的特点都是将“昂贵的”、“费时的”的资源维护在一个特定的“池子”中,规定其最小连接数、最大连接数、阻塞队列等配置,方便... 查看详情

linux编程之信号篇:异常监控必知必会

文章目录为什么要了解信号什么是信号信号有哪些如何发送/捕获信号发送killraisekillpgsigqueue监听signalsigaction信号处理函数的注意事项信号阻塞总结Thanks为什么要了解信号信号是UNIX系统进程管理非常重要的一环,下面这些场景都... 查看详情

synchrnoized必知必会,看看你都会哪些?

一、Synchronized:下订单超卖问题:如果多个线程同时碰到synchronized时,monitorEnter之前会产生竞争,只让一个线程进来,其他线程等待,保证线程有序的执行,但不能保证指令重排(volatile可以保证指令重... 查看详情

springmvc--必知必会(代码片段)

...求的控制器,而无需实现任何接口。同时它还支持RESTful编程风格的请求。SpringMVC是基于方法设计的,相比基于类设计的Struts2要稍微快一些 查看详情

6个必知必会高效python编程技巧

编写更好的Python代码需要遵循Python社区制定的最佳实践和指南。遵守这些标准可以使您的代码更具可读性、可维护性和效率。本文将展示一些技巧,帮助您编写更好的Python代码文章目录遵循PEP8风格指南1.遵守PEP8命名约定2.使... 查看详情

crypto必知必会(代码片段)

crypto必知必会最近参加了个ctf比赛,在i春秋,南邮方面刷了一些crypto密码学题目,从中也增长了不少知识,在此关于常见的密码学知识做个小总结!Base编码Base编码中用的比较多的是base64,首先就说一下Base64编码方式将字符串以... 查看详情

es6必知必会——class

1.在之前的JS面向对象编程中,如果定义一个构造函数,一般来说是这样:functionPerson(name,age)this.name=name;this.age=age;Person.prototype.say=function()return‘Mynameis‘+this.name+‘,Iam‘+this.age+‘yearsold‘;这种写法跟传统的面向对象语言(比如C++... 查看详情

大数据必知必会的-linux命令(代码片段)

用户的创建和删除命令用户创建和密码设置useradd用户名passwd用户名useradditheima#创建新用户itheimapasswditheima#设置用户itheima密码用户删除user-r用户名userdel-ritheima#删除用户itheima权限管理命令文件权限概述Linux操作系统是多任务多用... 查看详情

大数据必知必会的-linux命令(代码片段)

用户的创建和删除命令用户创建和密码设置useradd用户名passwd用户名useradditheima#创建新用户itheimapasswditheima#设置用户itheima密码用户删除user-r用户名userdel-ritheima#删除用户itheima权限管理命令文件权限概述Linux操作系统是多任务多用... 查看详情

es6必知必会——promise对象

Promise对象1.Promise对象是ES6对异步编程的一种解决方案,它有以下两个特点:Promise对象代表一个异步操作,它只有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败),并且该状态不会受外界的影响Pr... 查看详情

mysql必知必会,详尽入门,一文帮你学会sql必知必会(代码片段)

目录前言数据库的概念和术语SQL语言和组成DDLshow: 展示当前已有的数据库或者表create:创建一个数据库或者一个表 drop:删除表、数据库对象或者视图alter:修改现有的数据库对象,例如 修改表的属性或者字段(... 查看详情

android单例模式必知必会(代码片段)

...二、创建单例模式的方法2.1饿汉式2.2懒汉式2.2.1懒汉式(非线程安全)2.2.2懒汉式(线程安全)2.3双重检验锁2.4静态内部类2.5枚举小结三、扩展3.1防止反序列化3.2volatile关键字一、概念        单例模式是运用最广泛的设计模式之一&... 查看详情

《java虚拟机》必知必会的14个问题总结(内存模型+gc)(代码片段)

一、Java 概述1、Java相较于 PHP、C#、Ruby 等一样很优秀的编程语言的优势是什么?(1)体系结构中立,跨平台性能优越。Java程序依赖于 JVM 运行,javac编译器编译Java程序为平台通用的字节码文件(.class&#x... 查看详情

必知必会go语言学习路径

Go语言核心编程:Go命令行操作,变量、常量、类型、函数、包,数组、切片,指针、结构体、方法、接口,协程、管道、缓存区、选择、互斥锁,defer、panic、recover、error命令行工具:cobra、unfave/cliWeb... 查看详情

es6必知必会——generator函数(代码片段)

Generator函数1.Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同,通常有两个特征:function关键字与函数名之间有一个星号;函数体内部使用yield表达式,定义不同的内部状态//一个简单的Generator函数func... 查看详情