并发编程是一种让程序能够执行多个任务的编程技术,多个任务的执行时间有重合,如交替执行、同时执行等。相对于传统的从上到下依次同步执行代码,我们也称并发编程为异步编程。目前,常见的并发模型主要有两种,一是多线程模型,二是单线程事件循环模型。
一、多线程模型
1、进程和线程
了解线程之前,需要知道进程和线程的区别。Windows和Linux的进程和线程表现略有差异,以下对比仅针对Windows操作系统。
- 进程指操作系统中程序的一次执行过程,拥有独立的内存、文件等资源。一个应用可以有多个进程。
- 线程指进程中的执行单元,线程共享进程的全局变量和资源,但有独立的堆栈和上下文
- 进程是线程的容器,线程共享进程的资源
- 进程有入口线程,一个进程可以创建更多线程
- 进程和线程的图示:
2、多线程模型之操作系统线程
目前多线程的实现方式,主要有两种,一是操作系统线程,二是用户态协程。
操作系统线程由操作系统进行管理,每个线程有独立的堆栈和上下文,但共享进程的全局变量和资源。操作系统线程在内核态调度,可以充分利用多核CPU等硬件资源,从而提高系统的整体性能,特别适合CPU密集型操作和长期运行的任务;同时,线程间共享全局变量,操作更加简便,且不存在消息通讯的开销。它主要存在以下几个问题:一是线程占用内存资源比较大;二是线程切换的性能开销比较大;三是共享资源的争抢和死锁问题。
由于传统多线程的弊病,现代框架大多引入了async/await模式,实现线程的池化管理。线程被框架提前创建,并托管在后台管理的线程池中,由后台自动管理线程的创建和销毁、动态调整线程数量,从而实现线程的高效复用。比如C#的Web框架 - AspNetCore,使用async/await后,处理高并发的性能,在很多测试中,并不比Go这类实现协程的框架差。
本系列后面的章节,我们将以C#和AspNetCore为例,展开叙述操作系统线程。
3、多线程模型之用户态协程
协程由程序语言或运行时进行管理,是比线程更轻量级的并发单位,特别适合高并发的场景。协程的创建和切换,只需要在用户态完成,避免了进入内核态的开销,降低了上下文切换的成本,开销更低;同时,协程不需要分配大量的内核资源,通常一个协程只需要几KB内存,占用内存少,可以创建更加的协程。协程的主要问题,是不同协程之间的消息通讯,操作比较复杂,通讯开销也比较大。
协程的具体实现方式,又分“有栈”和“无栈”。比如Go语言提供了goroutine,实现了有栈协程,虽然每个协程有独立的栈空间,但仍然支持同时运行成千上万个goroutine,它鼓励使用“消息传递”方式来完成不同协程之间的通信和同步,通过使用channel可以安全的在多个goroutine之间传递数据。而Swift语言则提供了Task,表示一个独立的并发执行单元,实现了无栈协程,更加轻量,但不同协程之间的通信,必须通过Actor来实现。而仓颉语言则实现了M:N线程模型,M个用户态协程在N个操作系统线程上执行,本质上还是一种用户态协程。
本系列后面的章节,我们将以仓颉为例,展开叙述用户态协程。
二、单线程事件循环模型
1、事件循环机制
JS是为Web而生的,Web有个特点,就是IO密集型,请求数据>浏览器渲染DOM,任务非常简单。大多数情况下,单线程是足够了,但Web请求有个异步等待的问题,容易造成同步代码的阻塞。所以,JS诞生时就有了事件循环机制,但一开始是很简陋的,主要通过使用回调函数和setTimeout来实现,后来逐渐有了任务对列、Promise、async/await,事件循环机制才逐步完善。
2、Chrome浏览器
传统浏览器有一个很严重的问题,就是一旦代码出现长时阻塞或异常,整个浏览器就崩了。所以Chrome带来了多进程,子进程托管在主进程下面,每个浏览器Tab,都是一个子进程,即使崩了,也不会影响其它Tab和主进程,而且Tab之间的资源独立,也更加安全。Chrome不仅解决了浏览器“爱崩迪”的问题,还带来了新一代的JS执行引擎-V8。听名字,就很NB的感觉。确实,V8一出,一统江湖。
3、Node.js
传统后端存在线程切换的问题,在处理IO密集型这类高并发任务时,表现并不理想,而这类异步任务就特别适合用单线程事件循环机制来处理,所以有人将V8引擎带到了后端,并创造出了Node.js。
Node.js不但能够利用单线程事件循环机制,处理高并发的IO密集型任务,而且也能使用子进程,处理一些CPU密集型的计算任务。但是,子进程毕竟不是多线程,它有两个问题:一是子进程虽然托管在主进程下面,但它本质还是进程,占用独立的内存和文件资源,所以创建和销毁的开销远高于线程;二是进程之间,需要通过消息机制进行通讯,通讯开销也比较大,尤其是子进程之间的沟通,还需要借助主进程桥接。
本系列后面的章节,我们将以JS和Node.js为例,展开叙述单线程事件循环机制以及Node.js的子进程。
*这是一个系列文章,将全面介绍多线程、用户态协程和单线程事件循环机制,建议收藏、点赞哦!
*你在并发编程过程中碰到了哪些难题?欢迎评论区交流~~~
我是functionMC > function MyClass(){…}
C#/TS/鸿蒙/AI/中美科技竞争等问题,以及如何写Bug、防脱发、送外卖等问题,都可以私信提问哦!