这是我参与「第五届青训营」笔记创作活动的第九天
Go 并发原理
CSP并发模型
Go实现了两种模式,一种就是传统的并发模式,多线程共享内存,就是java和c++一些语言的多线程开发。
另一种是CSP(communicating sequential processes)并发模型,也是Go语言推荐并且独有的。
传统的多线程开发,是以共享内存实现通讯。相反,CSP并发模型是以通信实现共享内存。
Go的CSP并发模型是通过goroutine和channel来实现的
两个goroutinue通过channel一传一收。这便是CSP并发模型的基本模式。
Go并发模型的实现原理
操作系统根据资源访问权限的不同分为内核态和用户态,内核态可以访问CPU资源、IO资源、内存资源等硬件资源,为上层应用提供最基本的资源。用户态必须调用系统函数、或shell脚本来调用内核提供的资源。
用户程序所创建的线程一般就是用户态的线程。
线程模型的实现,可以分为以下几种方式:
用户级线程模型
多个用户态的线程对应着一个内核线程,程序线程的创建、终止、切换或同步等都需要自身来完成
内核级线程模型
直接调用系统的内核线程,所有线程的创建、终止、切换、同步都由内核来完成。C++就是这一种。
两级线程模型
这种模型是介于用户级线程和内核级线程之间的一种模式,一个进程可以对应多个内核级线程,但是进程中的线程不和内核线程一一对应。这种线程模型会创建多个内核级线程,然后由自己创建用户级线程,自身的用户级线程由自己本身程序调度,内核级的线程交由操作系统去调用。
Go语言的线程模型就是这样一种特殊的二级线程模型,称作GMP。
用户空间线程和内核空间线程之间的映射关系有:N:1,1:1和M:N N:1是说,多个(N)始终在一个内核线程上跑,context上下文切换确实很快,但是无法真正的利用多核。 1:1是说,一个用户线程就只在一个内核线程上跑,这时可以利用多核,但是上下文switch很慢。 M:N是说, 多个goroutine在多个内核线程上跑,这个看似可以集齐上面两者的优势,但是无疑增加了调度的难度。
Go语言实现模型GMP
- G指的是goroutine,本质上是一种轻量级的线程,可以叫做协程。是由go runtime管理着,不是操作系统的概念
- M指的是Machine,一个M直接关联了一个内核线程。
- P指的是processor,代表了M所需的上下文环境,可以把它看做一个局部的调度器。它是实现从N:1到N:M映射的关键。最多由GOMAXPROCS。
三者的关系:
当一个goroutine创建后,会优先放到P本地队列中,最大能放256个,如果为空。如果不为空则会放到全局队列当中。M想要运行任务,就得获取P,从P的本地队列获取G,M运行G,G执行完之后,M会从P获得下个G,依次往复。M会从P的本地队列中获取G,也会从全局队列中获取G
M代表了一个内核线程,操作系统调度器会调度M在cpu核上执行。
单从线程调度讲,Go语言相较于其他语言的优势在于OS线程是由操作系统调度的,goroutine则是由go运行时自己调度的,完全是在用户态执行的,不涉及内核态和用户态的频繁切换,包括内存块的分配和释放,都是在用户态维护着一块大的内存池。成本比调用OS线程低的多。
另一方面,充分利用了多核的硬件资源,近似的把goroutine均分到物理线程上,再加上本身goroutine的超轻量级,保证了goroutine调度方面的性能。
参考博客:
i6448038.github.io/2017/12/04/… www.liwenzhou.com/posts/Go/co…