前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Golang调度原理-浅析

Golang调度原理-浅析

作者头像
地球流浪猫
发布2023-10-14 19:23:49
2560
发布2023-10-14 19:23:49
举报
文章被收录于专栏:流浪猫的golang流浪猫的golang
Golang调度原理

1.进程和线程的区别

多线程并不能提高运行速度,但可以提高运行效率,让CPU的使用率更高

2.线程和协程的区别

问题1:线程是CPU调度的最小单位,同一个进程内有多个线程,CPU最多只能看到线程,协程在CPU如何运行的?

协程在CPU如何运行的?

答:Golang的协程是由Go调度器进行管理和调度的,调度器会将多个协程映射到少量的操作系统线程上执行。最终还是要在线程执行的。

线程和协程区别1:线程是CPU调度的,Go调度器进行管理和调度的

那为什么要多次一举,干嘛不直接运行线程的?

因为在很多线程的情况下,线程之间切换很浪费时间。而且多线程开发时候,设计会变得更加复杂,要考虑很多同步竞争等问题,如锁、竞争冲突,想要用好多线程不是那么容易。

线程之间上下文切换过程:

一个线程需要让出CPU执行时间,切换到另一个线程时,通常需要进行一次上下文切换,这包括保存当前线程的上下文(寄存器状态、堆栈等)并加载下一个线程的上下文。

大量的时间花费在保存和恢复寄存器和内存页表、更新内核相关数据结构等操作。

具体可以看:

https://baike.baidu.com/item/%E4%B8%8A%E4%B8%8B%E6%96%87%E5%88%87%E6%8D%A2

在这里插入图片描述
在这里插入图片描述

为了提高CPU利用率,减少多线程之间上下文切换花费时间,于是go设计出了协程。

golang实现的线程模型是两级线程模型(M:N)**

即: M个用户线程(协程)对应N个内核线程

线程模型共有三种:分别是一对一(1:1)模型多对一(N:1)模型多对多(M:N)模型

一对一(1:1)模型

1个协程绑定1个线程,协程的调度都由CPU完成,能利用多核。每个线程都有独立的寄存器集、堆栈和调用栈等资源,线程之间的调度和切换交给操作系统内核负责。如果这样做的话,干嘛不直接用多线程呢?

多对一(N:1)模型

多个协程被映射到一个操作系统线程上执行。协程直接切换又应用进程的调度器完成。但是这样做又用不了多核了。

多对多(M:N)模型

M个用户线程(协程)对应N个内核线程

既能让调度在用户空间完成,避免上下文切换。又能利用多核处理能力。可以这么理解:

有多个工人(协程)和多个工作台(线程)。每个工人可以在不同的工作台上完成不同的任务。一个工人可以负责原材料准备,另一个工人可以负责组装产品,还有一个工人可以负责包装。他们之间可以并行工作,互不打扰,如果有那个工作台空出来了,其他工人还可以去使用。这种模型可以更好地利用多核处理器的并行性,提高整体生产效率。

GMP模型

G: 协程(goroutine)

M: 线程(Machine),运行协程

P: 处理器(Processor),当P有任务时需要创建或者唤醒一个M来执行它队列里的任务,P拥有一个本地队列,存放这M。

刚才看了M:N模型,里面只有协程和线程的关系为何又多出来一个P?

从Golang 1.5版本开始,Golang引入了GMP模型,在1.5之前Golang使用的是GM模型+全局队列的方式。

全局队列存放多个协程,创建处理的协程首先会被加入到全局队列中,每次执行一个协程G的时候,内核线程M会从全局队列中获取一个协程G执行。多个线程刚好对应多个协程,刚好对应多个M:N关系。但是M从全局队列中读取协程的时候,需要加锁。频繁加锁解锁再高并发的时候就会代理一定性能问题。加锁解锁浪费时间,没有获取锁的M在等待中。

所以设计出一个P,在运行Go协程的过程中,P和M是1:1绑定关系。

M想要运行G的时候,首先从它绑定的P的本地队列中读取一个G来执行,然后再从全局队列中获取,如果从全局队列中没有获取到从其他P的队列偷取一部分P放到自己本地队列中。对比之前GM模型,M只能从全局队列获取,变成3种获取方式:从绑定的P本地队列中获取,从全局队列中获取,从其他P中偷取一半。这样做的目的就是减少大量的加锁解锁过程,提高效率。

从网上借个图:

在这里插入图片描述
在这里插入图片描述

P的数量:

  • 由启动时环境变量$GOMAXPROCS或者是由runtime的方法GOMAXPROCS()决定。假如P的数量等于CPU核心数,那么就是所有的核心都能运行协程。协程数量和任务多时,就能发挥出CPU最佳性能。PS:说一点,协程数量不是越多越好,GO协程调度小和内存占用小,不代表没有调度开销和内存开销。

M的数量:

M和P是1:1绑定,但是一个M阻塞后,P会和M解绑,P会从M的休眠队列中找一个空闲M或者重新创建一个M。M的总数量没有绝对关系,系统设置最大10000。

为什么M阻塞,P会和M解绑?

当线程阻塞时,它会释放CPU资源,当线程执行阻塞操作时,它会主动让出CPU,将执行权交给其他可运行的线程。如果解绑那么P对应那个CPU内核,在阻塞时,不能重复利用CPU资源

M阻塞完成后,会重新进入M队列休眠。

如果一个协程一直占用线程进行调度时,怎么办?

runtime.main中会创建一个额外m运行sysmon函数,

代码语言:javascript
复制
func sysmon() {
    lasttrace := int64(0)
    idle := 0 // how many cycles in succession we had not wokeup somebody
    delay := uint32(0)
    for {
        if idle == 0 { // start with 20us sleep...
            delay = 20
        } else if idle > 50 { // start doubling the sleep after 1ms...
            delay *= 2
        }
        if delay > 10*1000 { // up to 10ms
            delay = 10 * 1000
        }
        usleep(delay)

        ......
    }       
}

sysmon会进入一个无限循环, 第一轮回休眠20us, 之后每次休眠时间倍增, 最终每一轮都会休眠10ms,每10秒给M发一次消息,向正在运行的 goroutine 所绑定的的那个 M(也可以说是线程)发出 SIGURG 信号,抢占P。这个过程叫基于信号的抢占式调度。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-06-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Golang调度原理
  • 1.进程和线程的区别
  • 2.线程和协程的区别
  • 协程在CPU如何运行的?
  • golang实现的线程模型是两级线程模型(M:N)**
  • GMP模型
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档