进程、线程、多线程、并发、并行、阻塞、同步异步的区别
- 如何理解:程序、进程、线程、并发、并行、高并发?
- 怎样理解阻塞非阻塞与同步异步的区别?
- 同步和异步关注的是消息通信机制,返回结果是否跟随调用结束一起。
- 阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态,线程是否挂起。
- 并发并行:解决问题的方式,实现同步异步。CPU分配时间片,单核多核。
- 多线程:技术手段。
- 多线程编程VS单线程异步编程。
进程与线程
并发与并行
你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。 并发的关键是你有处理多个任务的能力,不一定要同时。 并行的关键是你有同时处理多个任务的能力。
并发(concurrency)
同一时间段,多个任务都在执行 (单位时间内不一定同时执行)。
并发主要是为了在单个线程阻塞时,切换到其它线程。
// nodejs
const userId = await doGetUserIdByToken(token);
const [userBasic, userScore, userProcessingOrders] =
await Promise.all([ // 并发执行下面3个任务
doGetUserBasic(userId),
doGetUserScore(userId),
doGetProcessingOrders(userId)
]);
const user = {...userBasic, ...userScore, ...userProcessingOrders};
return user;
并行(parallel)
单位时间内,多个任务同时执行。
如果想让一个事情变得容易【并行】,先得让制定一个【并发】的方法。倘若一个事情压根就没有【并发】的方法,那么无论有多少个可以干活的人,也不能【并行】
在软件系统中,【程序】是否能【并行】运行,要看物理上有多少个CPU核心可以同时干活(或者再扩展一下,有多少台可用的物理主机)。
并发执行的任务是解决同一个问题。
对于任务执行这个领域,对于两个任务A和B,如果我们说他们俩是【并发】的,这就要求不能在任务B里使用A的结果,也不能让A执行时使用B的结果。因此在执行层面,A可以在B之前执行,也可以在B执行,或者A和B交替执行,或者A和B【并行】的执行。不管执行层面怎么折腾,结果都是对的。 反过来,如果A的执行需要B的结果,那也就意味着A和B不是【并发】的,必须让B先执行完,A才可以开始。在实现层面,就可以用加锁、channel等方式来表达“先B后A”。
因此,在设计程序的时候,可以比如把计算和IO任务拆开设计一个【并发】的方法,然后利用CPU和网卡是两个零件来【并行】的跑。
比如你写了个Java程序,同时启动了4个线程,但CPU只有单核,那么同一时刻只有一个线程在运行。如果有4个CPU核心,那么可以做到4个线程完全【并行】运行。如果有2个核心,那么就处于一种中间态。比如你可以用“并发度=4“,”并行度=2“形容这种情况。
因此,想要在技术上精进的同学最好也要避免去那些公司。不管在哪里做事情,一定要保证自己做的直接和商业价值挂钩的事情才能有更多机会成长。
高并发
不像【并发】说的是“处理”,【并行】说的是“执行”,【高并发】说的是最终效果,极端一点【高并发】甚至并不一定需要【并行】。
除了【并发】和【并行】,【高并发】还需要:
- 数据表普遍被分库分表,否则单机放不下,或者查询性能不足
- 解决分布式事务
- 因为机器都可能坏,为了保证少数机器坏掉不会影响处理的性能,必须引入HA机制
- 因为系统都有极限,超过极限响应能力就会急剧下降。因此必须引入限流的方案来保护系统
- 这么复杂的系统会涉及到N个service,N个存储,N个队列…… 这些资源的管理又成为了新的问题,这又需要对集群和服务做管理
- 这么多服务,肯定要解决分布式的Tracing和报警问题
QPS(TPS)= 并发数 * 1(秒) / 平均响应时间(秒)
并发模型(了解)
- Fork & Join模型(大任务拆解为小任务并发的跑,结果再拼起来)
- Actor模型(干活的步骤之间直接发消息)
- CSP模型(干活的步骤之间订阅通话的频道来协作)
- 线程&锁模型(干活的人共享一个小本本,用来协作。注意小本本不能改乱套了,所以得加锁)
- ……
线程相关
线程的生命周期
RUNNABLE = READY + RUNNING
图源《Java 并发编程艺术》4.1.4 节
上下文切换
上下文:线程在执行过程中会有自己的运行条件和状态。
线程切换+保持上下文+恢复上下文。