golang协程为什么比线程轻量?

TrollpoeTiffany 发布于1年前

先理解下进程、线程跟协程的概念:

进程

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

线程

线程 有时被称为 轻量级进程(Lightweight Process) ,是程序执行流的最小单元,一个标准的线程由线程ID,当前指令指针(PC)、寄存器集合和堆栈组成,通常意义上,一个进程:u6709:一个到多个线程组成,各个线程之间共享程序的内存空间(包括代码段、数据段、堆等)及一些进程级的资源(如打开文件和信号)。一个经典的线程与进程的关系如图1-8所示:

golang协程为什么比线程轻量?

image.png

go协程

协程(coroutine)是Go语言中的 轻量级线程 实现,由Go运行时(runtime)管理。

进程、线程、协程的关系和区别:

  • 进程拥有自己独立的堆和栈,既不共享堆,亦不共享栈,进程由操作系统调度。

  • 线程拥有自己独立的栈和共享的堆,共享堆,不共享栈,线程亦由操作系统调度(标准线程是的)。

  • 协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。

并发与并行(Concurrency and Parallelism)

并发是指程序的逻辑结构。非并发的程序就是一根竹竿捅到底,只有一个逻辑控制流,也就是顺序执行的(Sequential)程序,在任何时刻,程序只会处在这个逻辑控制流的某个位置。而如果某个程序有多个独立的逻辑控制流,也就是可以同时处理(deal)多件事情,我们就说这个程序是并发的。这里的“同时”,并不一定要是真正在时钟的某一时刻(那是运行状态而不是逻辑结构),而是指:如果把这些逻辑控制流画成时序流程图,它们在时间线上是可以重叠的。

并行是指程序的运行状态。如果一个程序在某一时刻被多个CPU流水线同时进行处理,那么我们就说这个程序是以并行的形式在运行。(严格意义上讲,我们不能说某程序是“并行”的,因为“并行”不是描述程序本身,而是描述程序的运行状态,但这篇小文里就不那么咬文嚼字,以下说到“并行”的时候,就是指代“以并行的形式运行”)显然,并行一定是需要硬件支持的。

而且不难理解:

    1. 并发是并行的必要条件,如果一个程序本身就不是并发的,也就是只有一个逻辑控制流,那么我们不可能让其被并行处理。
    1. 并发不是并行的充分条件,一个并发的程序,如果只被一个CPU流水线进行处理(通过分时),那么它就不是并行的。

为什么协程比线程轻量?

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

线程并发执行流程:

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

go协程并发执行流程:

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

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

  • 2.go协程占用内存少

执行go协程只需要极少的栈内存(大概是4~5KB),默认情况下,线程栈的大小为1MB。

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

参考资料

查看原文: golang协程为什么比线程轻量?

  • organicrabbit
  • blacktiger