很多人反映多线程很难学习,很复杂;另外一方面,新技术框架层出不穷,学习过程疲于奔命,效果和方向却很茫然。
这是因为多数多线程的文章都偏重于技术框架API,而没有说清楚整体框架和思路,没有全局观,本文偏重于全局概念和思路。
一,多线程问题只有两种模式:多线程竞争和多线程协作。
线程只是一种表象,线程的本质是任务,协作和竞争的对象是数据。因此根本的问题是:任务和数据之间的关系。
多个任务和数据之间有两种关系:竞争关系和协作关系。
任务有多种体现形式:进程,线程,协程……他们是不同重量级(资源锁定/占用)的任务实现方式。
进程是有CPU和内存占用的任务;线程是占用CPU但是没有单独内存资源的任务——在一些OS中,进程和线程是没有区别的;协程是没有用户态和内核态切换的任务,是纯粹的逻辑块,是没有任何IO消耗的纯粹被事件驱动的逻辑块。
在LINUX中,原生进程是可以通过管道进行通讯的,但是JAVA线程不可以。他们通讯的唯一方式就是数据和信号。
影响软件性能的几个瓶颈资源分别是
1,IO(既数据)
2,线程依赖(即锁:线程对线程的依赖——本质还是对数据的依赖)
这两个因素会极大的影响(降低)系统的性能。全速的CPU逻辑运算速度是亿级别,千万级别的,涉及到IO和锁,就降低为*W级别了。
高性能软件必须考虑瓶颈资源的使用,必须满足以下三个原则
1,让瓶颈资源最大限度得到发挥
2,不做无用的事情
3,通过合理调度完成
二,在多线程竞争场景中,高性能的程序解决思路是无锁和弱化锁
尽量的无锁,细粒度锁,局部锁,短促锁……比如众所周知的ShareNothing架构,短事务,JUC包,读写分离……等,是这种思路的产物。
三,在多线程协作场景中,涉及到信号机制
1,线程锁模型
常见的是CountDown,Semphore等包,这其实是一种效率稍低的信号机制,因为它锁定线程本身
有没有无线程锁定的信号机制呢?
2,Actor模型就是这种思路的产物
Actor模型下有Vertx,akka等。他的线程是用完即释放的。极大的提升了线程资源的利用率,比线程锁模型有更大的并发处理能力
还有没有优化的空间呢?
有,12提升的是线程资源的利用率,还可以从IO上考虑
3,IO优化框架
Netty是一种IO线程框架,他的事件源是IO。它解决的问题是IO和线程速率不匹配下的资源利用率问题。
但是Netty弊病是依赖于IO,如果阻塞的事件源不是IO,那Netty提升不了效率
有没有不依赖于IO的事件线程机制呢?
这就是Actor模型。
Actor模型相比于12模型,他本质上分离了IO和任务块,他的任务调度依赖于纯粹的事件机制,而不是IO事件。akka/Vertx/MQ都可以归为此类(MQ消耗太高,传统观念上就不归为此类,但原理一致的)
那么这是否就是最高性能呢?
no,在操作系统层面,有可能一次任务切换回涉及到OS的用户态和内核态切换,由于OS的安全机制,会做数据拷贝。这也是极大的性能消耗。
消除OS层面线程上下文切换的消耗,这就是协程等框架的基本原理。
所以协程框架必须让业务系统告诉(协程)框架:阻塞点是什么,任务块是什么,这需要在逻辑书写层面引入一些标记来区分,告诉框架应该如何去调度。
4,CPU指令层面的优化
123措施基本已经考虑到机制了么?还没有,disruptor等框架还考虑对CPU底层指令进行冗余填充,以提升CPU层面的处理速度。
(disruptor的Ring结构是另外一个层面——锁优化——的问题,这里不赘述)
五,实际业务系统优化做法
但是,我们并不鼓励在业务代码中考虑性能优化问题。
理论上,软件系统本质上是一个函数f,是纯粹的逻辑块。它的速度是千万级的,是远远超过任何业务场景要求的。
导致软件系统性能降低的原因是
1,架构设计层面让IO和业务逻辑耦合
2,让业务逻辑程序员关注了太多依赖,而没有做好合适的模型设计和框架设计
3,引入了太多框架,中间件……这些所谓的高性能框架,他们无论性能多么高,都不可能高过CPU内核速度的。他们提升的是业务的弹性扩展能力和可用性冗余——这对于绝大多数公司99.99%都是镜花水月——设计良好的单机系统可以支撑100-10000的并发处理能力,远远在绝大多数公司的需求之上。
但是另外一方面,绝大多数公司真正需要的是业务系统的业务快速响应,灵活扩展能力——罕有架构师去考虑这真正是业务公司需要的问题。而把精力 都关注到不需要的性能上了。
再重复一遍:我们并不鼓励在业务系统中考虑性能优化问题。
多数的情况下,只要你不做无意义的是(参见高性能原则2)就好了。
同样的哲学下,请记得分布式架构设计第一原则,多线程第一原则,性能优化第一原则
1,不要分布式你的系统
2,不要多线程
3,不要性能优化
优秀的架构欢迎懒人,讨厌多事的人