美文网首页
go-并发剖析

go-并发剖析

作者: GGBond_8488 | 来源:发表于2020-03-02 10:51 被阅读0次

    进程、线程、协程

    进程:进程是系统进行资源分配的基本单元,有独立的内存空间
    线程:线程是cpu调度和分派的基本单位,线程依附于进程存在,每个线程共享父进程的资源
    协程:协程是一种用户态的轻量级线程,协程的调度完全由用户控制,协程切换只需要保存任务的上下文,没有内核的开销

    协程与多线程相比,其优势体现在:协程的执行效率极高。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。

    线程上下文切换

    由于中断处理,多任务处理,用户态切换等等原因会导致CPU从一个线程切换到另一个线程,切换过程中要保存一个线程的状态切换到另一个线程的状态。
    而这个上下文切换代价极高
    为了解决传统的多线程问题Golang引入了Goroutine
    Goroutine非常轻量

    • 上下文切换代价小:Goroutine上下文切换只涉及到三个寄存器(PC/SP/DX)的值的修改;而线程的上下文切换则需要设计模式转换(从用户态切换到内核态)、以及16个寄存器、PC、SP等寄存器的刷新
    • 内存占用少:线程栈空间通常是2m,而goroutine栈空间最小2k(可最大扩展到1G)

    Go并发调度的GMP模型

    在操作系统提供的内核线程之上,Go搭建了一个特有的两级线程模型。goroutine机制实现了M : N的线程模型
    ,goroutine机制是协程(coroutine)的一种实现,golang内置的调度器,可以让多核CPU中每个CPU执行一个协程。

    Go的调度器是如何工作的

    Go语言中支撑整个scheduler实现的主要有4个重要结构,分别是M、G、P、Sched, 前三个定义在runtime.h中,Sched定义在proc.c中。

    • Sched结构就是调度器,它维护有存储M和G的队列以及调度器的一些状态信息等。
    • M结构是Machine,系统线程,它由操作系统管理的,goroutine就是跑在M之上的;M是一个很大的结构,里面维护小对象内存cache(mcache)、当前执行的goroutine、随机数发生器等等非常多的信息。
    • P结构是Processor,处理器,它的主要用途就是用来执行goroutine的,它维护了一个goroutine队列,即runqueue。Processor是让我们从N:1调度到M:N调度的重要部分。
    • G是goroutine实现的核心结构,它包含了栈,指令指针,以及其他对调度goroutine很重要的信息,例如其阻塞的channel。

    Processor的数量是在启动时被设置为环境变量GOMAXPROCS的值,或者通过运行时调用函数GOMAXPROCS()进行设置。Processor数量固定意味着任意时刻只有GOMAXPROCS个线程在运行go代码。

    我们分别用三角形,矩形和圆形表示Machine Processor和Goroutine。

    GMP

    在单核处理器的场景下,所有goroutine运行在同一个M系统线程中,每一个M系统线程维护一个Processor,任何时刻,一个Processor中只有一个goroutine,其他goroutine在runqueue中等待。一个goroutine运行完自己的时间片后,让出上下文,回到runqueue中。 多核处理器的场景下,为了运行goroutines,每个M系统线程会持有一个Processor。


    单核

    Go运行时系统中的调度器的主要职责就是将G公平合理的安排到多个M上去执行
    在正常情况下,scheduler会按照上面的流程进行调度,但是为了充分利用线程的计算资源,Go调度器采取以下几种策略

    任务窃取

    为了提高Go并行处理能力,提高整体效率,当每个p之间的G任务不均衡时,调度器允许从GRQ(全局运行队列)LRQ(本地运行队列)中获取G


    任务窃取
    减少阻塞

    当正在运行的goroutine阻塞的时候,例如进行系统调用,会再创建一个系统线程(M1),当前的M线程放弃了它的Processor,P转到新的线程中去运行


    阻塞
    • 由于原子操作、互斥量、通道操作导致的阻塞,调度器将当前阻塞Goroutine切换出去,重新调度LRQ上的其他Goroutine
    • 由于网络请求阻塞 Go提供了网络轮询器(NetPoller)来处理网络请求和I/O操作的问题,通过NetPoller来进行网络系统调用,调度器可以防止Goroutine在进行这些操作时阻塞M。可以让M执行P中的LRQ的其他Goroutine和不需要创建M1,有助于减少操作系统上的负载。

    相关文章

      网友评论

          本文标题:go-并发剖析

          本文链接:https://www.haomeiwen.com/subject/rwxpkhtx.html