每当我看着大海的时候,我总想找人谈谈。但当我和人交谈时,我又总想去看看大海。                                                                                                                                                      —— 村上春树

go在语言层面对协程进行了原生的支持并且称为goroutine,这也是go语言强大并发能力的重要支撑。

本文并非一蹴而就,而是蓄势待发已久。

后续随着知识及理解的不断深入仍将持续补充进来。同时也欢迎各位留言探讨,互相学习。

目录

进程、线程、协程

为什么会选择协程

goroutine调度模型-GPM

并发与并行


进程、线程、协程

1,进程是系统资源分配的最小单位,线程是CPU调度的最小单位;

2,进程是线程的载体,一个进程可由多个线程组成,进程内的多个线程间可以相互通信;

3,进程的创建和销毁都是系统资源级别,因此是一种比较昂贵的操作;

4,线程是抢占式调度,协程是协作式调度,多线程无可避免的会带来频繁的CPU上下文切换,调度成本高,但抢占式调度由系统内核来完成的,用户态不需要参与,内核参与使得平台移植好;协作式调度中用户态协程会主动让出CPU控制权来让其他协程使用;

5,协程不和内核交互,协程是用户态轻量级线程;

6,线程与协程可简单的理解为捆绑关系,任意数量的用户态协程可以运行在任意数量的os线程上;用户创建的协程会被专门负责管理goroutine的processor接管调度,一个processor可对应N个协程;

7,原生的linux线程从进程栈分配空间,所分配的线程栈空间大小默认为8M:

而创建一个协程分配的空间是2k,空间可能会自动扩增。

为什么会选择协程

原生线程的调度成本高,那么go语言自己进行了接管调度---go runtime进行调度。

goroutine调度模型-GPM

源码位置:src/runtime/proc.go

// Goroutine scheduler
// The scheduler's job is to distribute ready-to-run goroutines over worker threads.
//
// The main concepts are:
// G - goroutine.
// M - worker thread, or machine.
// P - processor, a resource that is required to execute Go code.
//     M must have an associated P to execute Go code, however it can be
//     blocked or in a syscall w/o an associated P.

G:是对Goroutine的抽象。其中包括执行的函数指令及参数,G保存的任务对象、线程上下文切换,现场保护和现场恢复需要的寄存器(SP、IP)等信息,源码中runtime.g 结构表示的就是G。
M:os线程,即操作系统内核线程,也可以叫物理processor。源码中runtime.m 结构表示的就是M。通过M来找寻空闲的P从而给其下面挂需要执行的G;

M最大数量10000个(见源码proc.go的schedinit()函数:sched.maxmcount = 10000)。
P:执行go代码所需的处理器,是负责golang运行时的线程,负责执行goroutine,真正决定并发程度的是P,最大值为256个。初始化的时候一般会去读取GOMAXPROCS对应的值,如果没有显示设置,则会读取默认值。在Go1.5之后GOMAXPROCS被默认设置可用的核数,而之前则默认为1,源码中runtime.p 结构表示的就是P。

全局队列:维护一个队列用来存放等待执行的G,新创建的goroutine会先存放在本地队列,如果本地队列满了,则会将本地队列的G移动到全局队列里面,本地队列保存G数量默认不超过256个。所有M共享P全局队列。

调度器:Goroutine调度器中的P和OS调度器通过M相结合,操作系统调度器负责把内核线程分配到CPU的核上执行。当P有任务时需要创建或者唤醒一个系统线程来执行它队列里的任务,所以P和M需要进行绑定,所有的G实质上在M上执行。

每个P对应着一个M(线程想运行任务就需要先获取P),即M必须具有关联的P才能调度goroutine。新创建的goroutine会先存放在P的本地队列(图中灰色部分的G所在队列)中,等待调度器进行调度。

M与P绑定后,M先从P的运行队列(图中浅蓝色部分的G所在的待运行队列)中取出G,并切换到G的堆栈执行,当P的本地队列中没有G时,再从本地待调度队列中获取一个G分配给这个P,当本地队列中也没有待运行的G时,则尝试从其它的P偷部分G来执行(偷的时候偷一半)。

通过这种模型,go避免了大量的上下文切换。

——引入:如何减少上下文切换?(参考上下文切换 - Go语言中文网 - Golang中文社区)

  • 无锁并发编程,锁的获取与释放会发生上下文切换,多线程时会影响效率。无锁并发编程就是将数据分块,每个线程处理各自模块。比如LongAdder中部分代码。
  • CAS算法,并发编程时通过CAS算法更新数据,而不必加锁。如Java的atomic包下的工具类。
  • 使用最少线程,减少不必要的线程创建,自定义线程池。
  • 使用协程,在单线程中维护多任务调度,处理任务间切换,Golang对于协程的使用貌似很强大。

调度执行与现场恢复:M执行过程中随时会发生上下文切换。当发生上线文切换时需要对执行现场进行保护,以便下次被调度执行时进行现场恢复。Go调度器M的栈保存在G对象上,只需要将M所需要的寄存器(SP、PC等)保存到G对象上就可以实现现场保护。当这些寄存器数据被保护起来就随时可以做上下文切换了,在中断之前把现场保存起来。如果此时G任务还没有执行完,M可以将任务重新丢到P的任务队列,等待下一次被调度执行。当再次被调度执行时,M通过访问G的vdsoSP、vdsoPC寄存器进行现场恢复(从上次中断位置继续执行)。

源码对应

M:

type m struct {g0      *g     // goroutine with scheduling stackmorebuf gobuf  // gobuf arg to morestackdivmod  uint32 // div/mod denominator for arm - known to liblink// Fields not known to debuggers.procid        uint64       // for debuggers, but offset not hard-codedgsignal       *g           // signal-handling ggoSigStack    gsignalStack // Go-allocated signal handling stacksigmask       sigset       // storage for saved signal masktls           [6]uintptr   // thread-local storage (for x86 extern register)mstartfn      func()curg          *g       // current running goroutinecaughtsig     guintptr // goroutine running during fatal signalp             puintptr // attached p for executing go code (nil if not executing go code)nextp         puintptroldp          puintptr // the p that was attached before executing a syscallid            int64mallocing     int32throwing      int32preemptoff    string // if != "", keep curg running on this mlocks         int32dying         int32profilehz     int32spinning      bool // m is out of work and is actively looking for workblocked       bool // m is blocked on a notenewSigstack   bool // minit on C thread called sigaltstackprintlock     int8incgo         bool   // m is executing a cgo callfreeWait      uint32 // if == 0, safe to free g0 and delete m (atomic)fastrand      [2]uint32needextram    booltraceback     uint8ncgocall      uint64      // number of cgo calls in totalncgo          int32       // number of cgo calls currently in progresscgoCallersUse uint32      // if non-zero, cgoCallers in use temporarilycgoCallers    *cgoCallers // cgo traceback if crashing in cgo callpark          notealllink       *m // on allmschedlink     muintptrmcache        *mcachelockedg       guintptrcreatestack   [32]uintptr // stack that created this thread.lockedExt     uint32      // tracking for external LockOSThreadlockedInt     uint32      // tracking for internal lockOSThreadnextwaitm     muintptr    // next m waiting for lockwaitunlockf   func(*g, unsafe.Pointer) boolwaitlock      unsafe.Pointerwaittraceev   bytewaittraceskip intstartingtrace boolsyscalltick   uint32freelink      *m // on sched.freem// these are here because they are too large to be on the stack// of low-level NOSPLIT functions.libcall   libcalllibcallpc uintptr // for cpu profilerlibcallsp uintptrlibcallg  guintptrsyscall   libcall // stores syscall parameters on windowsvdsoSP uintptr // SP for traceback while in VDSO call (0 if not in call)vdsoPC uintptr // PC for traceback while in VDSO call// preemptGen counts the number of completed preemption// signals. This is used to detect when a preemption is// requested, but fails. Accessed atomically.preemptGen uint32dlogPerMmOS
}

并发与并行

文末,顺带聊聊并发与并行

并行:你在吃饭,来了个电话,无关紧要,你一边吃一边说;

并发:你在吃饭,来了个电话,因为一些因素必须通完电话才能继续吃饭(你的身体先由打电话支配使用),通完电话后继续吃饭(身体切换到可以吃饭)。

浅谈go协程及其调度模型相关推荐

  1. swoole一键携程化mysql_【SWOOLE系列】浅淡SWOOLE协程(二) 一键协程化

    前言 是的,我又来了,我带着我的文章表情包回来. 再这感谢swoole大佬们的点赞和转载,让我短暂的感受到了什么要叫高光时刻. 背景 我相信大部分人一开始用swoole的协程的时候都会再协程里写了一大 ...

  2. 线程/协程/异步的编程模型(CPU利用率为核心)

    最近看了一个b站博主的视频https://www.bilibili.com/video/av64066246/讲到了线程/协程/异步的编程模型,这里做下记录 1.线程 上篇文章有聊到进程和线程的关系, ...

  3. python多线程调度_python并发编程之进程、线程、协程的调度原理(六)

    进程.线程和协程的调度和运行原理总结. 系列文章 进程.线程的调度策略介绍 linux中的进程主要有三种调度策略: 优先级调度:将进程分为普通进程和实时进程: 先进先出(队列)调度:实时进程先创建的先 ...

  4. python并发编程-进程池线程池-协程-I/O模型-04

    目录 进程池线程池的使用***** 进程池/线程池的创建和提交回调 验证复用池子里的线程或进程 异步回调机制 通过闭包给回调函数添加额外参数(扩展) 协程*** 概念回顾(协程这里再理一下) 如何实现 ...

  5. 计算机网络:浅谈HTTP与TCP/IP四层模型

    计算机网络:浅谈HTTP与TCP/IP四层模型 其实对于我这样的非科班出身来说,理解算法不是最难的.最难的就是计算机网络这种无法一口吃完的内容.因此专门抽空写点这方面的内容.其实本来只是想写一篇HTT ...

  6. 十、Go协程的调度,互斥锁,计数器和线程池

    @Author:Runsen 在字节面试中,我见过:GO语言中的协程与Python中的协程的区别?其实就是要我讲解Go中GMP机制.我表示很多都用过,但是底层不了解. 那时我只知道与传统的系统级线程和 ...

  7. Cpython解释器下实现并发编程——多进程、多线程、协程、IO模型

    一.背景知识 进程即正在执行的一个过程.进程是对正在运行的程序的一个抽象. 进程的概念起源于操作系统,是操作系统最核心的概念,也是操作系统提供的最古老也是最重要的抽象概念之一.操作系统的其他所有内容都 ...

  8. python(40)- 进程、线程、协程及IO模型

    一.操作系统概念 操作系统位于底层硬件与应用软件之间的一层.工作方式:向下管理硬件,向上提供接口. 操作系统进行进程切换:1.出现IO操作:2.固定时间. 固定时间很短,人感受不到.每一个应用层运行起 ...

  9. 5,线程池,进程池,协程,IO模型

    今日内容: 1,线程池 2,进程池 3,协程 4,IO 模型 服务端要满足这三个条件: 1,24小时不间断的提供服务 2,能够支持高并发 3,要有固定的IP地址和端口在服务端这个地方会出现阻塞态情况: ...

最新文章

  1. 一个理想主义者关于爱情和美女、事业与金钱的疯人痴语
  2. 问题解决java.lang.IllegalArgumentException at org.springframework.asm.ClassReader
  3. Spring WebClient vs. RestTemplate
  4. python dicom 器官分割_图像识别 | 使用Python对医学Dicom文件的预处理(含代码)
  5. Pandas库(2):数据的统计分析
  6. 使用 SASS Mixin 编写 clean code
  7. power bi 日期计算_PowerBI 动态计算周内日权重指数
  8. Android Service、IntentService,Service和组件间通信
  9. 熊仔科技Steamduino PIC18F46J50主控板 部分原理图
  10. WPF自定义控件与样式(5)-Calendar/DatePicker日期控件自定义样式及扩展
  11. PowerPC E500 MMU详解
  12. Android Studio 初探
  13. linear-gradient 立体背景 按钮_2020高邮亮光背景墙8D立体逼真方兴装饰了解更多
  14. 关于DBC文件的创建(DBC文件系列其一)
  15. 排错万能金钥匙之Linux系统应用
  16. 【操作系统-进程】PV操作——哲学家问题
  17. mysql dede arctiny_dede标签的使用
  18. 相机对焦和调焦距的区别是什么 ?
  19. 蓝色荧光染料Monobromobimane (mBBr) 单溴二胺71418-44-5
  20. 包头新松机器人_煤矿机器人现状及发展方向

热门文章

  1. 开车回家过年需要注意的那些事
  2. python人脸口罩识别训练模型_【每天进步一点点】基于ModelArts,跑通口罩检测案例(Python版本)——模型训练与转化部分...
  3. 二元二次方程例题_2015中考数学精选例题解析二元二次方程组
  4. 关于合并apk和odex的实践
  5. BugkuCTF Crypto wirte up
  6. 人工智能在医疗领域的应用:预测疾病和提高治疗效果
  7. 鼠须管基本配置(佛系专供)
  8. 简单几招模拟网络超时情况
  9. 技巧: Excel数据如何快速录入?
  10. Xiaojie雷达之路---详解ADCBuf driver源码