1. 协程是什么 ?

在go语言中,协程被认为是轻量级的线程, 和线程不同的是,操作系统内核
感知不到协程的存在, 协程的管理依赖于Go语言运行时自身提供的调度器
同时Go语言中的协程是从属于某一个线程的.

在这里提出一个问题 : **为什么Go语言需要在线程的基础上抽象出协程的概念, 而不是直接操作线程 ? **

回答这个问题就需要深入的了解线程与协程的区别

1.1 调度方式

协程是用户态的。协程的管理依赖Go语言运行时的调度器。
同时,Go语言中的协程是从属于某一个线程的,协程与线程的对应关系为M:N,即多对多 .
如图所示, Go语言调度器可以将多个协程调度到一个线程中,一个协程也可能切换到多个线程中执行。

在这里插入图片描述

1. 2 上下文切换的速度

上下文指的是什么 ?
举个例子 :
当某个线程占用CPU时间过长时, 操作系统的调度器就会强制下线依此来保证每个线程在一段时间内运行的时间差别不大的. 那么此时进行调度, 就需要发生上下文的切换, 因为我们下次再运行这个线程时, 需要记录上一次运行的各种条件. 例如记录上一次的重要寄存器值, 进程状态等等. 这些都存储在线程控制块中(TCB).

上下文更为浅显的意思就是 : 所需要依赖的环境, 这次的线程退出我们需要记录其重要的值和信息以便下次再上CPU时, 可以从上一次的末尾开始, 就不必重新开始执行. 新上来的线程, 也需要加载上一次执行的各种数据以便这次执行更方便

线程的调度, 需要操作系统状态的转换, 发送调度, 需要从用户态转变为内核态, 当切换到下一个要执行的线程时, 又需要从内核态转变为用户态

==================================================

从这里我们就可以开始讲一下线程和协程这两者之间的上下文切换速度

协程的速度要快于线程
其原因在于协程切换不用经过操作系统用户态与内核态的切换
并且Go语言中的协程切换只需要保留极少的状态和寄存器变量值
而线程切换会保留额外的寄存器变量值(例如浮点数寄存器)

1.3 调度的策略不同

线程的调度在大部分时间是抢占式的,操作系统调度器为了均衡每个线程的执行周期,会定时发出中断信号强制执行线程上下文切换。
而Go语言中的协程在一般情况下是协作式调度的,当一个协程处理完自己的任务后,可以主动将执行权限让渡给其他协程。这意味着协程可以更好地在规定时间内完成自己的工作,而不会轻易被抢占。当一个协程运行了过长时间时,Go语言调度器才会强制抢占其.

1.4 栈空间大小

我们知道,线程是有固定的栈的,基本都是2MB,当然,不同系统可能大小不太一样,但是的确都是固定分配的。这个栈用于保存局部变量,用于在函数切换时使用。但是对于goroutine这种轻量级的协程来说,一个大小固定的栈可能会导致资源浪费:比如一个协程里面只print了一个语句,那么栈基本没怎么用;当然,也有可能嵌套调用很深,那么可能也不够用。
  所以go采用了动态扩张收缩的策略:初始化为2KB,最大可扩张到1GB。