.Net 多线程开发优化实践
互联网产品中微服务、高并发已经成为最基本的要求。所谓高并发就是在同一时刻处理多个服务请求。为了提高高并发场景下的系统稳定性,负载均衡、消息队列等框架和技术应运而生,有效的缓解了高并发对系统整体压力。无论是这些框架和技术,还是日常并发编程都离不开一个基础:多线程。以下我们就聊一聊多线程这种最基础的处理并发的方式。
无论是云服务还是传统应用,不可或缺的组成部分就是线程。为了实现系统的并行处理业务,Microsoft自Windows NT引入了多线程。经过多年的积累与发展,无论是线程的稳定性还是使用的便捷性都有巨大的提升。随着.NET的普及,线程的创建和使用也更加方便。Windows系统中的线程也动辄数千个线程在运行。
通过多线程的应用,能够让操作系统更加顺畅,让系统可以并行处理业务。那是不是我们就可以无限度的使用线程了呢?天下没有免费的午餐,线程的使用也是有代价的。特别是高并发的情况下。我们对线程的使用需要更加谨慎。线程是如何运行的,它们又有哪些资源上的损耗。只有了解了线程内部运行机制,我们才能更好的利用线程、发挥其作用。
一、线程的内存开销
单个CPU在同一时刻只能执行一个指令,为了实现线程“并行”执行的要求。Windows通过时间片控制线程间调度执行。为了线程间调度,操作系统为每个线程分配一定的内存,用于保存线程的初始化信息、上下文信息、入参信息等。每个线程都包含以下几个部分:
1、线程内核对象
2、线程环境块
3、用户模式栈
4、内核模式栈
以x64操作系统为例(x86相差无几),其中线程内核对象占用1k内存、线程环境块占用4kB内存、用户模式栈占用1M内存、内核模式栈占用24k内存。每个线程都需要占用1M+的内存。
如上图所示,大量开启线程,必然造成内存的“泄露”。如果进程运行在x86平台,因其内存最大寻址为2G,当开启线程为1500左右,就会出现经典的OutOfMemoryException。虽然x64基本上没有了此约束,但是也并不是可以无限度的滥用线程,毕竟内存资源是有限的。
特来电互联互通系统中,进行充电桩状态推送是并发较大的一个场景。当充电桩设备状态发生变化,例如由插枪到启动充电、启动充电到停止充电等等。一个充电桩状态发生变化后,需要及时推送给所有此充电桩关联的互联互通厂商。随着充电桩和厂商数量的增多,推送的数据量也成倍数增加。以下是数据推送的TPM图。
从图中可见,推送TPM基本在2000左右,高峰时刻为6000左右。推送服务受接收方影响较大,不可控因素更多。如果每次推送都启动一个线程,然后在线程中进行数据推送,系统的线程数就完全不可控。同时建立2000个线程,单单这一个功能线程消耗的内存资源就有2G以上,当内存资源被耗尽的时候就会出现系统宕机等问题。如果不启动线程,我们就无法很好的保证一次终端状态变化,相关的商户都能第一时间接收到推送数据。权衡资源与需求,要求我们合理利用资源,保证互联互通商户及时收到推送信息的同时,保证系统的线程数的稳定。
分布式、微服务从宏观框架上帮我们解决了这个问题;合理使用线程,在微观编程上保证了系统的稳定。
首先将推送信息在MQ消息队列缓冲,消息队列将推送信息分发至分布的各个节点进行处理,通过消息队列,将请求进行了缓冲和分发,有效的缓解了各个节点压力。
为了有效的进行消息消费,保证商户及时接收到推送信息,各个节点程序内部,我们采用了二次缓存,将需要推送的信息缓存至本地内存中,然后每个互联互通商户启动一个线程进行推送。这样既保证了各个商户都能及时收到推送的信息,也极大的控制了线程数量,避免的线程数的激增。
通过框架和线程的配合,有效的保证了推送消息的及时送达,并保证了系统线程的稳定。
二、线程的执行性能
线程在创建以及线程间调用时需要执行寄存器、内存等操作,这些操作会造成一定的性能损耗。损耗主要包括以下两个方面:
1、线程创建及销毁时的通知
2、线程上下文切换
Windows在进程中创建和销毁线程时,会调用进程中加载的所有非托管DLL的DllMain方法,以便通知相关DLL进行资源初始化或销毁。虽然托管的DLL中不存在DllMain方法,因此不会存在此问题。但Windows每个进程动辄就是加载几百个Dll,其中不乏大量的非托管Dll,因此这部分损耗也是不小的。
线程对执行性能影响最大的是上下文切换。对于一个CPU来说,在某一个时刻只会有一个线程在执行,而我们感觉中多线程同时执行(不讨论多CPU),功劳就是Windows的线程调度。Windows会给每个线程分配一个时间片,用于线程运行。此时间片到期后,Windows将执行另一线程,此时Windows就会进行上下文切换。Windows进行上下文切换时,将执行如下操作:
1、将当前线程的CPU寄存器的值保存在线程的内核对象的上下文结构中。
2、从线程集合中获取一个可执行的线程。
3、将所获取的线程的上下文结构加载到CPU寄存器中。
4、CPU执行所选的线程。
Windows大约每30ms执行一个上下文切换,但当Windows在一个时间片结束后继续执行同一个线程则不会出现上下文切换。为了减少上下文切换带来的损耗,我们要合理的使用线程,只有需要的时候才创建线程,避免上下文的切换。
三、线程的调度
Windows进行线程调用是按照其优先级进行的。Windows中的线程优先级为0(最低)~31(最高)。如存在可以执行的优先级为31的线程时,就不会执行0~30的线程。
为了让开发人员更好的理解优先级,而不直接设置线程优先级。Microsoft对进程和线程进行优先级类描述。其中进程为了6类:Idle、Below Normal、Normal、Above Normal、High、Realtime。线程级别为7类:Idle、Lowest、Below Normal、Normal、Above Normal、Highest、Time-Critical。线程的优先级是由进程优先级类和线程优先级类一起确定的,根据进程优先级类和线程优先级类构成线程的基本优先级。对应关系为:
进程优先级 |
||||||
线程优先级 |
Idle |
Below Normal |
Normal |
Above Normal |
High |
Realtime |
Idle |
1 |
1 |
1 |
1 |
1 |
16 |
Lowest |
2 |
4 |
6 |
8 |
11 |
22 |
Below Normal |
3 |
5 |
7 |
9 |
12 |
23 |
Normal |
4 |
6 |
8 |
10 |
13 |
24 |
Above Normal |
5 |
7 |
9 |
11 |
14 |
25 |
Highest |
6 |
8 |
10 |
12 |
15 |
26 |
Time-Critical |
15 |
15 |
15 |
15 |
15 |
31 |
以上表中有部分优先级并没有体现,这是因为Windows将其预留,分配给零页线程和驱动线程等。
通过进程和线程优先级类确定的线程优先级是不是在其运行过程中就不会改变了呢?不是的。线程初始化时,线程优先级等于基础优先级。但为了保证线程的可响应性,Windows会动态对线程优先级进行调整。
如图所示,任务管理器中一个线程基本优先级为8,即进程优先级类和线程优先级类为Normal,但其运行过程中的优先级为10,Windows已经进行了优先级调整。(对于16~31的线程,系统不会提升其优先级)
由此可见如果我们需要快速响应某个操作我们可以提高其优先级,但通常情况下我们不应该提高其优先级,以免影响其他线程的执行。只有需要及时响应并执行时间很短时才考虑调高其线程优先级,集中资源办急事。
一般桌面软件(例如wps线程数为25个左右),线程数并不高,并且不会有大的波动,而在高并发系统的每个节点程序中也至少有200以上个线程在运行。如果我们不能很好的使用这些线程,或者无节制的创建线程。线程数将呈倍数增长,系统复杂度和内存等资源都将出现不可控,影响系统运行,甚至出现宕机。因此越是高并发越要合理使用线程。
随着CPU的多核、超线程等的使用,从硬件底层对线程的损耗已经进行了一定的弥补。我们的CPU利用率,基本都是在10%左右,远远没有发挥其真正价值。了解线程,就是为了更恰当、合理的使用线程。当我们需要最大限度的利用线程就需要对这些基础知识深入了解。基础知识是航标,虽然不需要一直盯着它,但它却指示着我们的方向。
.Net 多线程开发优化实践的更多相关文章
- 爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结
本文由爱奇艺技术团队原创分享,原题<爱奇艺Android客户端启动优化与分析>. 1.引言 互联网领域里有个八秒定律,如果网页打开时间超过8秒,便会有超过70%的用户放弃等待,对Andro ...
- 长连接锁服务优化实践 C10K问题 nodejs的内部构造 limits.conf文件修改 sysctl.conf文件修改
小结: 1. 当文件句柄数目超过 10 之后,epoll 性能将优于 select 和 poll:当文件句柄数目达到 10K 的时候,epoll 已经超过 select 和 poll 两个数量级. 2 ...
- 海量数据和高并发下的 Redis 业务优化实践
本文内容是我在 6 月 23 日参加的深圳 GIAC 技术大会上演讲的文字稿. 观众朋友们,我是来自掌阅的工程师钱文品,掘金小册<Redis 深度历险>的作者.今天我带来的是分享主题是:R ...
- 携程App的网络性能优化实践
首先介绍一下携程App的网络服务架构.由于携程业务众多,开发资源导致无法全部使用Native来实现业务逻辑,因此有相当一部分频道基于Hybrid实现.网络通讯属于基础&业务框架层中基础设施的一 ...
- web前端开发最佳实践笔记
一.文章开篇 由于最近也比较忙,一方面是忙着公司的事情,另外一方面也是忙着看书和学习,所以没有时间来和大家一起分享知识,现在好了,终于回归博客园的大家庭了,今天我打算来分享一下关于<web前端开 ...
- Java多线程开发技巧
很多开发者谈到Java多线程开发,仅仅停留在new Thread(...).start()或直接使用Executor框架这个层面,对于线程的管理和控制却不够深入,通过读<Java并发编程实践&g ...
- 数据库优化实践【MS SQL优化开篇】
数据库定义: 数据库是依照某种数据模型组织起来并存在二级存储器中的数据集合,此集合具有尽可能不重复,以最优方式为特定组织提供多种应用服务,其数据结构独立于应用程序,对数据的CRUD操作进行统一管理和控 ...
- Glow Android 优化实践
了解 Glow 的朋友应该知道,我们主营四款 App,分别是Eve.Glow.Nuture和Baby.作为创业公司,我们的四款 App 都处于高速开发中,平均每个 Android App 由两人负责开 ...
- [转]Android开发最佳实践
——欢迎转载,请注明出处 http://blog.csdn.net/asce1885 ,未经本人同意请勿用于商业用途,谢谢—— 原文链接:https://github.com/futurice/and ...
随机推荐
- [解读REST] 5.Web的需求 & 推导REST
衔接上文[解读REST] 4.基于网络应用的架构风格,上文总结了一些适用于基于网络应用的架构风格,以及其评估结果.在前文的基础上,本文介绍一下Web架构的需求,以及在对Web的关键协议进行设计和改进的 ...
- 【转载】jQuery动画中的queue()函数
原文链接:http://www.cnblogs.com/hh54188/archive/2011/04/09/1996469.html 原文摘要:当你使用一系列的动画效果(如hide,show),这些 ...
- SAP smartform 实现打印条形码
先在SE73里定义一个新的BARCODE,注意一定要用新的才可以,旧的是打印不出来的. 然后定义一个SMARTFORM的样式,把你定义的BARCODE放到字符样式里面去. 再做SMARTFORM就可以 ...
- 【JDK1.8】JDK1.8集合源码阅读——总章
一.前言 今天开始阅读jdk1.8的集合部分,平时在写项目的时候,用到的最多的部分可能就是Java的集合框架,通过阅读集合框架源码,了解其内部的数据结构实现,能够深入理解各个集合的性能特性,并且能够帮 ...
- mysql单独可连接,php连接mysql失败之 Can't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock' (2)
此种解决方案使用场景: 1,mysql单独可以启动而且远程工具也可以连接 2,php无法连接. 3,find / -name mysql.sock 可以找到文件路径 4,报错 Can't connec ...
- 《剑指Offer》面试题5-替换空格
题目:请实现一个函数,把字符串中的每个空格替换成"%20".例如输入"We are happy.",则输出"We%20are%20happy.&quo ...
- spring MVC 环境搭建
绿色版Spring MVC(单纯的springMVC) 一.导包,为了获取请求数据多添加一个包 二.web.xml配置 <?xml version="1.0" encodin ...
- LeetCode 501. Find Mode in Binary Search Tree (找到二叉搜索树的众数)
Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred ...
- request的getServletPath(),getContextPath(),getRequestURI(),getRealPath("/")区别
假定你的web application 名称为news,你在浏览器中输入请求路径: http://localhost:8080/news/main/list.jsp 则执行下面向行代码后打印出如下结果 ...
- Vue源码后记-更多options参数(1)
我是这样计划的,写完这个还写一篇数据变动时,VNode是如何更新的,顺便初探一下diff算法. 至于vue-router.vuex等插件源码,容我缓一波好吧,vue看的有点伤. 其实在之前讲其余内置指 ...