Golang中GMP的介绍参考:/2020/12/27221039-008-gmp-model.html

操作系统会在物理处理器上调度线程来运行,而Go语言的运行时会在逻辑处理器P上调度goroutine来运行。每个逻辑处理器都分别绑定到单个操作系统线程M。在1.5版本以后,Go语言的运行时默认会为每个可用的物理处理器分配一个逻辑处理器。这些逻辑处理器会用于执行所有被创建的G。即便只有一个逻辑处理器,Go也可以以神奇的效率和性能,并发调度无数个G。

有时正在运行的G需要执行一个阻塞的系统调用,如打开一个文件。当这类调用发生时,线程M和G会从逻辑处理器上分离,该线程M会继续阻塞,等待系统调用的返回。与此同时,这个逻辑处理器就失去了用来运行的线程M。所以,调度器会创建一个新线程,并将其绑定到该逻辑处理器上。之后,调度器会从本地运行队列里选择另一个G来运行。一旦被阻塞的系统调用执行完成并返回,对应的G会放回到本地运行队列,而之前的线程会被回收到线程池。如果一个G需要做一个网络I/O调用,流程上会有些不一样,此时,G会和逻辑处理器P分离,并移到集成了网络轮询器的运行时。一旦该轮询器指示某个网络读或者写操作已经就绪,对应的G就会重新分配到逻辑处理器上来完成操作。

调度器对可以创建的系统线程M的数量没有限制,但语言运行时默认限制每个程序最多创建10 000个线程。这个限制值可以通过调用runtime/debug包的SetMaxThreads方法来更改。如果程序试图使用更多的线程,就会崩溃。

进程、线程、协程

现代计算机的操作系统模式是一种多任务系统,操作系统接管了所有的硬件资源,并且本身运行在一个受硬件保护的级别。所有的应用程序都以进程(process)的方式运行在比操作系统权限更低的级别,每个进程都有自己独立的地址空间,使得进程之间的地址空间相互隔离。CPU由操作系统统一进行分配,每个进程根据进程的优先级的高低都有机会得到CPU,但是如果允许时间超出了一定的时间,操作系统会暂停该进程,将CPU资源分配给其他等待的进程。这种CPU的分配方式称为:抢占式,操作系统可以强制剥夺CPU资源并且分配给它认为目前最需要的进程。如果操作系统分配给每个进程的时间都很短,即CPU在多个进程间快速地切换,从而造成了很多进程都在同时运行的假象。

线程有时被称为轻量级进程(Lightweight Process),是程序执行流的最小单元,通常意义上,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)及一些进程级的资源(如打开文件和信号)。

协程(coroutine)是Go语言中的轻量级线程实现,由Go运行时(runtime)管理。(有人说:协程就像是一种特殊的函数。)

作者Rob Pike说:一个Goroutine是一个与其他goroutines 并发运行在同一地址空间的Go函数或方法。一个运行的程序由一个或更多个goroutine组成。它与线程、协程、进程等不同。它是一个goroutine。

不一样的协程

协程(CoRoutine)是一种轻量级的用户态线程。简单来说,线程(thread)的调度是由操作系统负责,线程的睡眠、等待、唤醒的时机是由操作系统控制,开发者无法决定。使用协程,开发者可以自行控制程序切换的时机,可以在一个函数执行到一半的时候中断执行,让出CPU,在需要的时候再回到中断点继续执行。因为切换的时机是由开发者来决定的,就可以结合业务的需求来实现一些高级的特性。

协程的特点:

  1. 有独立的栈空间
  2. 共享程序堆空间
  3. 调度由用户控制
  4. 协程是轻量级的线程

协程的优点:

  1. 代码编辑简单。可以将异步处理逻辑代码用同步的方式编写,将多个异步操作集中到一个函数中完成。
  2. 单线程模式,没有线程安全的问题,不需要加锁操作。
  3. 性能好。协程是用户态线程,切换更加高效。

为什么协程比线程轻量?

一旦创建完线程,你就无法决定他什么时候获得时间片,什么时候让出时间片了,你把它交给了内核。而协程编写者可以有:可控的切换时机和很小的切换代价。从操作系统有没有调度权上看,协程就是因为不需要进行内核态的切换。

go协程调用跟切换比线程效率高

线程并发执行流程:

线程是内核对外提供的服务,应用程序可以通过系统调用让内核启动线程,由内核来负责线程调度和切换。线程在等待IO操作时线程变为unrunnable状态会触发上下文切换。现代操作系统一般都采用抢占式调度,上下文切换一般发生在时钟中断和系统调用返回前,调度器计算当前线程的时间片,如果需要切换就从运行队列中选出一个目标线程,保存当前线程的环境,并且恢复目标线程的运行环境,最典型的就是切换ESP指向目标线程内核堆栈,将EIP指向目标线程上次被调度出时的指令地址。

go协程并发执行流程:

不依赖操作系统和其提供的线程,golang自己实现的CSP并发模型实现:M, P, G

go协程也叫用户态线程,协程之间的切换发生在用户态。在用户态没有时钟中断,系统调用等机制,因此效率高。

go协程占用内存少

执行go协程只需要极少的栈内存(最新版默认2KB,可以根据程序执行自动伸缩),默认情况下,线程栈的大小为1MB。

goroutine就是一段代码,一个函数入口,以及在堆上为其分配的一个堆栈。所以它非常廉价,我们可以很轻松的创建上万个goroutine,但它们并不是被操作系统所调度执行。

(完)