当前位置: 首页 > news >正文

Go 协程

Goroutine

Goroutine 算是Go 语言实现的超轻量级线程,但它的核心在于它不是由操作系统内核直接管理的,而是由 ​Go 运行时runtime自己管理和调度的,这才是他的高效之处。Go初始栈 2KB,动态增长。

Goroutine的特点优点
1.Go协程的​内存占用小​,初始栈为2KB,动态增长。
2.Go协程创建销毁快​:用户态操作,无系统调用开销。
3.切换开销小​:调度在用户态由运行时完成,只有必要的上下文切换(如寄存器值),而不是完整的线程上下文切换。并且通过合理的调度时机来避免无意义的切换。
4.​高并发能力​:GMP 模型通过本地队列减少了锁竞争,使得数万甚至百万级的并发任务可以被高效管理。

为何有这些特点,我们可以从以下三个核心层面来理解Goroutine:
​1. 创建与内存。
2. 调度模型(GMP)​。
3. 调度时机。

1. 创建与内存:go协程为啥如此轻量?

操作系统线程(OS Thread)​​:
a.创建和销毁需要进入内核态,系统调用开销大。
b.每个线程有自己固定且较大的栈空间(例如 1-2MB),主要用于处理最复杂的函数调用。大量线程会消耗大量内存。

​Goroutine​:
a.创建和销毁完全在用户态由 Go 运行时负责,开销极小,就是几个函数调用,而不是系统调用。
b.初始栈空间非常小(通常约 2KB),并且采用按需分配/缩放的动态栈。当栈空间不足时,运行时会自动为它分配一块更大的栈,并把旧栈的内容复制过去。这使内存使用非常高效,可以轻松创建数十万甚至上百万个 Goroutine。

2. 核心调度模型:GMP 模型

Go 的调度器采用了一个叫做 ​GMP​ 的三级模型。

​G - Goroutine​:就是我们要调度的协程本体。它包含了函数、指令指针、栈等信息。
​M - Machine(工作线程)​​:代表着操作系统的内核线程。它是真正在 CPU 上执行代码的实体。所有 Goroutine 最终都要跑在某个 M 上。

M 的数量默认上限是 10000,一般远超 CPU 核心数,因为大部分 M 会阻塞在系统调用上。

​P - Processor(调度器/上下文)​​:P 是 G 和 M 之间的纽带,是 Go 调度器的关键创新。

把 P 理解为一个本地运行队列和执行上下文。它维护着一个本地的 Goroutine 队列(称为 local run queue)。

P 的数量默认等于当前程序的 GOMAXPROCS值(通常是 CPU 的逻辑核心数,比如 16 核机器就是 16)。这限制了真正并发运行的 Goroutine 数量。
GOMAXPROCS可以通过命令设置:export GOMAXPROCS=4,也可以在代码中调用:runtime.GOMAXPROCS(16)

​GMP关系与工作流程​:

开始运行时,M和P的工作流程:
请添加图片描述

​绑定关系​:在程序运行时,Go 运行时会创建 GOMAXPROCS个 P。每个 P 都持有一个本地的 G 队列。一个 M 要想执行 G,必须先获取一个 P,即 M 与 P 绑定。

​后续流程​:

1.当执行 go func()时,会创建一个新的 G。

2.这个 G 会优先被放入当前 M 所绑定的 P 的本地运行队列中。(这里为什么会放入当前M所绑定的P的本地运行队列中,因为go选择的是1.默认路径,就近原则高效。如果当前队列满了,会把拿当前队列的一半任务和这个新任务一起放到公共的全局队列G中)

3.M 会从它绑定的 P 的本地队列中取出一个 G 来执行(LIFO 策略。a.如果绑定的P的本地队列空了怎么办呢?---他会从全局队列G中加锁取一批:len(global_queue)/GOMAXPROCS + 1的量到自己的本地队列。b.如果全局队列也空了呢?---那就随机从其他的P中偷取一半的数量到本地队列。如果其他的P都空呢?---那就从网络轮询器中获取那些已就绪的G来执行。如果网络轮询器也获取不到呢?---那就休眠吧,P和M散伙,各回各家各找各妈,P放回空闲P列表,M放入空闲M列表,等有任务来了再继续)。

4.当 G 执行完毕,M 会再从 P 的队列中取下一个 G。如此循环。

3. 调度时机:何时切换 Goroutine?

Go 的调度是协作式的,但带有强烈的抢占式倾向。它不会像操作系统那样用时间片轮转来强制切换线程。Goroutine 的调度发生在以下关键时刻:

1.主动挂起(协作式点位)

Goroutine 在遇到一些特定操作时,会主动让出 CPU 使用权。这些操作会成为调度器的“切入点”:

a.​I/O 操作​:如网络请求、文件读写。
​b.channel 操作​:如向 channel 发送或接收数据时遇到阻塞。
​c.执行 runtime.Gosched()函数​:主动放弃当前执行,让给其他 Goroutine。
d.系统调用​:如访问操作系统功能。
​e.垃圾回收(GC)​​:GC 的某些阶段需要暂停所有 Goroutine。

2.被动抢占(防止饿死)

如果一个 Goroutine 执行一个非常耗时的计算任务(比如一个死循环,且内部没有上述的协作点),它不会主动让出 CPU,这会导致其他 Goroutine 被“饿死”。为了解决这个问题,Go 调度器实现了基于信号的异步抢占。

流程:
1.Go 运行时会向执行长时间任务的 M 发送一个特殊的信号(如 SIGURG)。
2.M 收到信号后,会中断当前正在执行的 G,并执行一个预注册的信号处理函数。
3.在这个处理函数中,调度器有机会将当前的 G 挂起,放回运行队列,然后让 M 去执行其他的 G。这样就实现了“强制”切换,保证了公平性。

可以举个例子
go运行时就像一个销售公司运作。这个销售公司有很多销售项目组(P),也有很多销售顾问(M),每个销售项目组都有一块黑板写着代办的任务(G)。还有一个公共栏记录那些没有分派出去的公共任务(全局G,这个地方是一些公共的代办任务,哪个销售项目组没任务可以来这里抢任务做)。
1.当一个任务(G)被创建之后就近分派给销售项目组(P)接手这个任务,一旦这个P任务满了就放到公共栏。
2.销售顾问(M)做任务,先从销售项目组(P)中取任务(G),拿到任务则执行。如果销售组(P)没有任务就去公共栏取任务,如果公共栏也没任务,从其他销售项目组(P)中偷取一半的任务(G)执行,如果其他销售项目组(P)也没有任务,当牛马的要去看看老板有没有其他安排了,如果没安排那就把这个销售项目组放到空列表中,记录完结,销售顾问可以去外面休息喝口西北风了,等来活了继续牛马的循环工作。

一个P同时只能绑定一个M,一个项目组实际有很多销售顾问,把这些销售顾问看成一个整体就行了

http://www.dtcms.com/a/554189.html

相关文章:

  • 做网站时怎样图片上传怎么才能让图片不变形有什么插件吗淄博住房和城乡建设局网站
  • leetcode1312.让字符串成为回文串的最少插入次数
  • 宜春做网站 黑酷seo快递网站建站需要什么
  • org.apache.commons.lang3都有什么常用的类
  • edas会议投稿显示格式错误+消除浮动块下面的空白
  • 宁波建设网站公司北京seo案例
  • 虚拟网站仿制教程河南国控建设集团招标网站
  • viewerjs+vue3 using typescript
  • U81904 【模板】树的直径
  • 如何将React自定义语法转化为标准JavaScript语法?
  • 自己做网站主机wordpress 引号被转义
  • 做营销网站推广快速开发安卓app
  • 文件基础操作详解
  • 【22】C语言 - 二维数组详解
  • 嵌入式项目代码架构与分层笔记
  • 自己房子做民宿挂什么网站数字今天科技 网站
  • 建设ca网站aws wordpress 集群
  • Rust数据类型(上):标量类型全解析
  • BPC EPM表单常规设置
  • 关于C++递归函数和指针部分
  • 基于STM32的智能天气时钟
  • 传奇网站建设网站开发公用头部
  • 安徽省建设厅官方网站黄世山电商办公室
  • 做的网站上更改内容改怎么办科技公司logo设计图片
  • 飞腾D2000/8在Ubuntu20.04下压力测试
  • 深度学习模型部署:将 TensorFlow 模型转为 TFLite 适配移动端
  • 新版ubuntu中sac安装问题(缺少libncurses5)
  • 使用Docker搭建YApi接口管理平台
  • 建立网站的成本林州网站建设服务
  • 齐博企业网站创建网站成功案例