协程 | 青训营笔记

75 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天

说到协程,那我们应该先来简单的看看进程与线程,进程是资源分配的基本单位,一个进程中可以有多个线程,线程共享进程进程的内存等资源,线程是操作系统资源调度的最小单位,同一进程中的线程切换要比进程切换更快,开销更小

在操作系统中有两个概念非常容易混淆,那就是并发并行,并发是指在同一时间段内,多个任务都能执行完毕,在任意时刻,只有一个任务被执行,并行是指在同一时刻,多个任务同时执行

在Go语言中,协程被认作为是轻量级线程,与线程不一样的是内核感受不到协程的存在,协程的管理依赖于Go自己的调度器,协程与线程对应的关系为N:M,协程的上下文的切换大概为0.2微秒,线程是他的5-10倍

在Go中启用一个协程非常简单,只需要在函数前加一个关键字go就可以

func SayHello(){
    fmr.Println("hello")
}
    
func main(){
    fmt.Println("111")
    go SayHello()
    time.Sleep(1 * time.Second)
}

GMP

相信很多接触go的同学都听过一个词叫做GMP,那么GMP究竟是什么呢,这是Go对协程调度的策略,G代表的是goroutine,M是线程,P代表的是逻辑处理器,Go将运行队列分为局部运行队列和全局运行队列,M每次从局部队列中获取一个goroutine,当局部队列没有goroutine时,就在全局队列中拿,当P的局部队列满的时候,就拿出一半的协程放到全局队列中,当然,为了防止一直循环局部队列中的协程而导致全局队列中的协程不被执行,go的调度器设置了一种策略,那就是每执行61次就在全局队列中获取一个协程来运行,当局部和全局队列中都获取不到可运行协程时,那么该M会通过一些策略在别的P中拿到可运行的协程来运行

调度时机

看完了GMP是不是好奇协程会在什么时候发生调度呢?

主动调度

协程可以主动让出自己的执行权限,通过用户在代码中实行runtime中的GOsched函数来实现,但大多数情况下不需要执行此函数,因为调度器会自己判断协程是否需要被抢占

被动调度

当协程在休眠,channel堵塞,执行垃圾回收暂停时,被动让渡自己的执行权力,被动调度因为协程处于Gwaiting状态,所以还需要加一个唤醒机制

抢占调度

为了让每个协程都有执行的机会,Go语言在初始化时会启用一个特殊的线程,每间隔10ms就检测是否有准备就绪的网络携程,并放进全局队列中,系统监控服务也会判断当前协程是否执行时间过长或者处于系统调度状态,如果是的话,则会抢占该协程的执行