C#中实现并发的几种方法的性能测试

时间:2022-08-27 22:55:10

C#中实现并发的几种方法的性能测试

0x00 起因

去年写的一个程序因为需要在局域网发送消息支持一些命令和简单数据的传输,所以写了一个C/S的通信模块。当时的做法很简单,服务端等待链接,有用户接入后开启一个线程,在线程中运行一个while循环接收数据,接收到数据就处理。用户退出(收到QUIT命令)后线程结束。程序一直运行正常(当然还要处理“TCP粘包”、消息格式封装等问题,在此不作讨论),不过随着使用的人越来越多,而且考虑到线程开销比较大,如果有100个用户链接那么服务端就要多创建100个线程,500个用户就是500个线程,确实太夸张了(当然实际并没有那么多用户)。由于TCP通信并不是每时每刻都在进行着的,因此可以把所有客户端连接存储到一个列表中,通过轮询的方式依次开启一个线程进行数据接收,接收完毕后释放线程,这样可以充分利用线程池,避免大量线程消耗内存和CPU。

轮询的方式通过线程池实现了线程的复用,可以肯定的是在资源开销上肯定是小很多的,但轮询的方式在单位时间内的处理次数会不会比保持线程的方式少很多呢,本测试将解决这个疑问。

0x01 实验方法

IDE:VS2015

.Net Framework 4.5

接收数据的对象如下所示

C#中实现并发的几种方法的性能测试

通过ReceiveData方法接收数据,每次接收只有1%的可能性收到数据,通过创建N个对象接收数据来模拟一个TCP服务端处理N个连接的情况。毕竟TCP通信不是随时进行的,当然这个百分比可以调整。程序输出的内容包括每秒执行了多少次接收操作,接收到数据的线程编号和接收到的内容等。

0x02 保持线程的并发

保持线程的并发非常直观,就是每建立一个对象就开一个新线程循环进行ReceiveData操作,当接收到数据就把相关信息输出到主界面上。代码如下所示:

C#中实现并发的几种方法的性能测试

0x03 使用ThreadPool轮询并发

方法是使用一个List(或其他容器)把所有的对象放进去,创建一个线程(为了防止UI假死,由于这个线程创建后会一直执行切运算密集,所以使用TheadPool和Thread差别不大),在这个线程中使用foreach(或for)循环依次对每个对象执行ReceiveData方法,每次执行的时候创建一个线程池线程来执行。代码如下:

C#中实现并发的几种方法的性能测试

0x04使用Task轮询并发

方法与ThreadPool类似,只是每次创建线程池线程执行ReceiveData方法时是通过Task创建的线程。代码如下所示:

C#中实现并发的几种方法的性能测试

0x05 使用await轮询并发

方法与ThreadPool类似,只是每次创建线程池线程执行ReceiveData方法时是通过await等待操作。代码如下:

刚开始在foreach中写了await导致线程阻塞,但因为ReceiveData()中测试时为了尽量拉开差距没有让线程睡眠以模拟线程操作,导致没有意识到这个问题,多谢 @逸风之狐 提醒。

修改后代码如下所示,这样测试方法就可以立即返回了。不过async/await确实不是用来干这个的。

C#中实现并发的几种方法的性能测试

0x06 使用Parallel并发

这是FCL提供的一种方法,Parallel.ForEach中每次方法都是异步执行,执行采用的是线程池线程。代码如下所示:

C#中实现并发的几种方法的性能测试

0x07 测试结果

创建500个对象来模拟500个连接的情况。其中测试结果中的每秒接收次数会有个波动范围,主要参照百位以上。使用线程池线程的几个方法(ThreadPool、Task、await、Parallel)中程序的线程数略有差别,可能跟执行环境有关,难以表明实质性差异。其中await因为线程切换导致线程执行时间略长,使得线程池需要多创建一些线程。

1、保持线程的并发

C#中实现并发的几种方法的性能测试

平均每秒接收8654次数据。在任务开始后会创建500个线程,由于每个线程都需要单独的栈空间来执行,内存消耗较大。频繁切换线程也会加重CPU的负担。

2、ThreadPool轮询并发

C#中实现并发的几种方法的性能测试

平均每秒接受9529次数据。由于实现了线程池线程的复用,无需创建太多线程,内存没有出现波动,CPU消耗也比较均匀。

3、Task轮询并发

C#中实现并发的几种方法的性能测试

平均每秒接收9322次数据,由于Task也是基于线程池的封装,因此与ThreadPool结果差别不大。

4、await轮询并发

C#中实现并发的几种方法的性能测试

平均每秒接收4150次。await也是使用线程池线程,所以在内存开销和线程数上与其他使用线程池线程的方法没有太大差别。但await在等待完毕后会将执行上下文从线程池线程切换回调用线程,因此CPU开销较大。

5、Parallel并发

C#中实现并发的几种方法的性能测试

看名字就知道这个设计出来就是应用于这种使用环境的,平均每秒接收9387次数据,也是使用线程池线程,所以内存和CPU消耗与ThreadPool和Task差不多。但不需要自己写foreach(for)循环,只要写循环体即可。

6、补充测试

经测试随着ReceiveData()耗时不断增加,轮询方式的优势越来越小。表现就是刚开始线程执行效率很低,需要花费时间慢慢赶上去。因为线程池中的初始线程不够用,需要创建更多的线程池线程,线程池线程创建起来没有Thread那么快,不过当线程池中的线程数量逐渐满足需求之后,轮询的优势就又体现出来了。

测试1:测试同样500个线程,有1%的可能接收到数据,但收到数据时模拟执行操作耗时100毫秒,程序刚开始效率很低,花了大概12秒左右,当线程数增长到54个时基本稳定可以满足需求,效率也越来越高。

C#中实现并发的几种方法的性能测试

测试2:测试同样500个线程,有1%的可能接收到数据,但收到数据时模拟执行操作耗时500毫秒,程序刚开始效率同样很低,花了大概150秒左右,当线程数增长到97个时基本稳定可以满足需求,效率也越来越高。

C#中实现并发的几种方法的性能测试

0x08 结论

首先明显能看出来的是使用轮询的方式比保持线程能节省很多资源,特别是内存。而且在处理效率上轮询的方式(每秒接收9300-9500次)比保持线程还要高(每秒8600+)。因此在这种并发模型下应该使用轮询的方式以节省资源并提高并发效率。

实际上硬拿await来比较是不太公平的,await被设计出来就不是应用于这种场景的。不管是之前关于异步的测试还是并发的测试,基于线程池的方案相差都不大。因此思路对了的情况下使用ThreadPool总是没错的。但有些类型把ThreadPool包装了以更好适应某些特殊场景,因此有了Task、await、Parallel等。而在这次的测试条件下显然Parallel是最合适的,与直接使用ThreadPool相比资源开销和执行效率一样,但代码更少。

在补充测试中也能看到,不同的运行环境对运行效率的影响还是很大的,因此还是要针对自己的环境做针对性更强的测试以采用更合适的方法。例如在我的使用环境中,服务端TCP消息的转发和部分命令的处理耗时都是非常短的。同样假设最高同时在线500个用户,这500个用户也不会是同事登陆的,所以也不会存在线程池初始线程严重不够用的情况。随着用户慢慢登陆,线程池线程根据需求慢慢增加,这样创建线程池线程增加的耗时就不那么明显了。所以在我的使用环境下轮询的方式无疑是合适的。因此刚开始对ReceiveData()只设置了接受数据的概率,没有模拟延迟。大家有需求的可以把测试程序下下来根据实际情况调整最大并发数、接收到数据的概率和接收数据的耗时以进行测试。

0x09 相关下载

测试代码下载链接:https://github.com/durow/TestArea/tree/master/AsyncTest/ConcurrenceTest

C#中实现并发的几种方法的性能测试的更多相关文章

  1. PHP中获取星期的几种方法

    PHP中获取星期的几种方法   PHP星期几获取代码: 1 date(l); 2 //data就可以获取英文的星期比如Sunday 3 date(w); 4 //这个可以获取数字星期比如123,注意0 ...

  2. CSS中隐藏内容的3种方法及属性值

    CSS中隐藏内容的3种方法及属性值 (2011-02-11 13:33:59)   在制作网页时,隐藏内容也是一种比较常用的手法,它的作用一般有:隐藏文本/图片.隐藏链接.隐藏超出范围的内容.隐藏弹出 ...

  3. 关于iOS去除数组中重复数据的几种方法

    关于iOS去除数组中重复数据的几种方法   在工作工程中我们不必要会遇到,在数组中有重复数据的时候,如何去除重复的数据呢? 第一种:利用NSDictionary的AllKeys(AllValues)方 ...

  4. JS去除数组中重复值的四种方法

    JS去除数组中重复值的四种方法 1 /// <summary>            o[this[i]] = "";  }      }       newArr.p ...

  5. php获取数组中重复数据的两种方法

    分享下php获取数组中重复数据的两种方法. 1,利用php提供的函数,array_unique和array_diff_assoc来实现 <?php function FetchRepeatMem ...

  6. js中数组去重的几种方法

    js中数组去重的几种方法         1.遍历数组,一一比较,比较到相同的就删除后面的                 function unique(arr){                 ...

  7. IOS开发中数据持久化的几种方法--NSUserDefaults

    IOS开发中数据持久化的几种方法--NSUserDefaults IOS 开发中,经常会遇到需要把一些数据保存在本地的情况,那么这个时候我们有以下几种可以选择的方案: 一.使用NSUserDefaul ...

  8. 在ASP&period;NET Core中构建路由的5种方法

    原文链接 :https://stormpath.com/blog/routing-in-asp-net-core 在ASP.NET Core中构建路由的5种方法 原文链接 :https://storm ...

  9. nodejs高并发大流量的设计实现&comma;控制并发的三种方法

    nodejs高并发大流量的设计实现,控制并发的三种方法eventproxy.async.mapLimit.async.queue控制并发Node.js是建立在Google V8 JavaScript引 ...

随机推荐

  1. 一则奇怪的案例处理:ORA-00257&colon; archiver error&period; Connect internal only&comma; until freed

    前天,业务反应数据库不能连接 在操作系统通过字符串尝试登陆数据库报:ORA-00257: archiver error. Connect internal only, until freed 解决思路 ...

  2. ZooKeeper 笔记&lpar;2&rpar; 监听数据变化

    ZK中的每个节点都可以存储一些轻量级的数据,这些数据的变化会同步到集群中的其它机器.在应用中程序员可以添加watcher来监听这些数据的变化,watcher只会触发一次,所以触发过后想要继续监听,必须 ...

  3. linux工程管理工具make入门

    一.make工具的功能 1.主要负责一个软件工程中多个源代码的自动编译工作 2.还能进行环境检测.后期处理等工作: 3.make工具可以识别出工程中哪些文件已经被修改,并且在再次编译的时候只编译这些文 ...

  4. mysql查看&&num;39&semi;datadir&&num;39&semi;目录

    mysql查看创建的数据库的数据,包含表等存放的目录,可以输入下面指令查看: show variables like 'datadir'

  5. 基于visual Studio2013解决C语言竞赛题之0204实数求值

     题目

  6. 【原】Linux设备网络硬件管理

    遇到网络问题时候,一般情况下,我们第一反应是查找软件方面问题,但排查之后,软件没有问题的时候,我们就需要排查硬件方面工作是否正常. 我们可能需要查询网卡设备本身的状态,查询网卡是否有数据包发送接收: ...

  7. &lbrack;HAOI 2007&rsqb;上升序列

    Description 对于一个给定的S={a1,a2,a3,…,an},若有P={ax1,ax2,ax3,…,axm},满足(x1 < x2 < … < xm)且( ax1 &lt ...

  8. C&num; - 2017微软校园招聘笔试题 之 MS Recognition&lbrack;待解决&rsqb;

    MS Recognition 在线提交: hihoCoder 1402 http://hihocoder.com/problemset/problem/1402 类似: OpenJudge - I:P ...

  9. Java部分概念理解

    第1部分 方法 1.1 方法基本概念 1) 方法:用于封装一段特定功能代码,尽可能一个方法至只实现一个基本功能,相当于C语言中的函数: 2) 方法可以多次调用: 3) 避免代码冗余,便于维护,便于团队 ...

  10. 在阿里云Centos下LNMP环境搭建

    首先,需要安装C语言的编译环境,因为Nginx是C语言编写的.通常大多数Linux都会默认安装GCC,如果没有的话,可以如下安装. 安装make: yum -y install gcc automak ...