大多数Go语言的使用者认为Go语言是互联网时代的C语言,为应用而生,当然我也这么觉得哈哈哈,听君一席话真是如听君一席话。
目前可以实现并发执行的模型主要有四种,多进程、多线程、基于回调的非阻塞/异步IO、协程。
-
多进程。多进程是在操作系统层面进行并发的基本模式。同时也是开销最大的模式。在Linux平台上,很多工具链正是采用这种模式在工作。比如某个Web服务器,它会有专门的进程负责网络端口的监听和链接管理,还会有专门的进程负责事务和运算。这种方法的好处在于简单、进程间互不影响,坏处在于系统开销大,因为所有的进程都是由内核管理的。
-
多线程。多线程在大部分操作系统上都属于系统层面的并发模式,也是我们使用最多的最有效的一种模式。目前,我们所见的几乎所有工具链都会使用这种模式。它比多进程的开销小很多,但是其开销依旧比较大,且在高并发模式下,效率会有影响。
-
基于回调的非阻塞/异步IO。这种架构的诞生实际上来源于多线程模式的危机。在很多高并发服务器开发实践中,使用多线程模式会很快耗尽服务器的内存和CPU资源。而这种模式通过事件驱动的方式使用异步IO,使服务器持续运转,且尽可能地少用线程,降低开销,它目前在Node.js中得到了很好的实践。但是使用这种模式,编程比多线程要复杂,因为它把流程做了分割,对于问题本身的反应不够自然。
-
协程。协程(Coroutine)本质上是一种用户态线程,不需要操作系统来进行抢占式调度,且在真正的实现中寄存于线程中,因此,系统开销极小,可以有效提高线程的任务并发性,而避免多线程的缺点。使用协程的优点是编程简单,结构清晰;缺点是需要语言的支持,如果不支持,则需要用户在程序中自行实现调度器。目前,原生支持协程的语言还很少。
- Go语言就是原生支持协程的语言,Go语言的线程用过的都知道是由go关键字来开启,由channel来完成线程之间的通信,那么再底层是由什么框架来实现的呢?
- Go语言的线程模型实际是协程的一种实现,叫goroutine机制。这个机制可以简单分为四个部分,用户线程,系统线程、用户线程调度器和内核调度器。
- 其中用户线程由goroutine来维护,简称为G,包含了栈、指针等,系统线程可以简单理解为CPU处理核心,简称为M-Machine,用户现场调度器控制G和M的对应关系,简称为P-Processor,内核调度器调度系统线程和CPU核心的对应关系。为什么说Go语言适用于高并发呢,答案就在这里,因为G可以同时维护多个用户线程,而G可以同时存在多个,P负责调度G与M的对应关系。简单点说就是多个线程对应多个协程,多个协程对应多个系统线程,多个系统线程对应多个核心处理器,从而充分的利用计算资源。
- 实际工作中,为了方便理解,这里简化了实际流程,P的作用远不止这么简单,核心和系统线程的对应一般也是多对多的。假如我们在两个双核CPU(4个核心)上同时发起了16次运算请求-16个用户线程,这16个x线程将进入4个G,也就是4个goroutine队列,大概如下图
- 用户发起的线程进入不同的Goroutine队列,由P来调度G与M的对应,最后再CPU上完成运算。
- 当然实际上的并发会有很多问题,比如线程阻塞、抢占式调度等问题,这些问题接下来再讨论。