news 2026/9/3 3:19:09

Go进阶之协程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Go进阶之协程

1.协程的概念:

1.1基本概念:

1).进程:

进程是应用启动的实例.每个进程都有自己独立的内存空间.不同的进程通过进程间的

通信方式来通信.

2).线程:

线程从属于进程.每个进程至少包含一个线程.线程是CPU调度的基本单位.多个线程之

前共享进程资源并通过共享内存等线程间的通信方式通信.

3).协程:

协程可以理解为一种轻量级的线程.与线程相比.协程不受操作系统的调度.协程调度由

用户应用程序提供.协程调度器按照调度策略把协程调度到线程中运行.Go应用程序的

协程调度由runtime包提供.使用go关键字即可创建协程.

1.2协程的优势:

在高并发应用中频繁的创建线程会造成不必要的开销.所以有了线程池技术.在线程池

中预先保存一定量的线程.新任务将不再创建线程的方式去执行.而是将任务放到任务

队列中去执行.线程池中的线程将不断地从任务队列中取出任务并执行.这样可以减少

线程的创建和销毁带来的开销.

任务队列中的每个任务往往代表一个函数.线程池中的worker线程不断的从任务队列

中取出任务并执行.而worker线程则交给操作系统进行调度.如果worker线程执行的

任务中发生系统调用.则操作系统会将该线程设置为阻塞状态.也就意味着线程怠工.消

费任务队列中的worker线程变少了.所以线程消费任务队列的能力变弱了.如果大部

分worker线程都在进行系统调用.会造成队列堆积.

解决这个问题的思路就是重新审视线程池中的数量.增加线程池中线程的数量.提高消

费任务的能力.但随着线程数量增多.会有过多的线程争抢CPU资源.消费能力还是会下

降.

过多的线程会造成COU上下文的频繁的切换.工作态中的用户协程则能大大的减少上

下文的切换的开销.协程调度器把可运行的协程逐个调度到线程中执行.同时把阻塞的

协程调度出线程.有效的避免了线程的频繁切换.达到了使用少量线程实现高并发的效

果.

2.调度模型:

2.1线程模型:

线程可以分为用户线程和内核线程.用户线程由用户创建 同步和销毁.内核线程则有内

核来管理.根据管理方式不同分为三种模型.

1).N:1模型.N个用户线程运行在一个内核线程中.优点是用户线程上下文切换快.缺点

是无法充分利用COU多核算力.

2).1:1模型.即每个用户线程对应一个内核线程,优点是充分利用CPU的算力.缺点是线

程上下文切换较慢.

3).M:N模型.即前两种模型的组合.M个用户线程(协程)运行在N个线程中.优点是充

分利用CPU的算力且协程上下文切换快.缺点是该模式调度算法较为复杂.

2.2Go调度模型:

Go调度模型中包含三个关键实体.machine简称M.processor简称P和goroutine

简称G.

M:工作线程.它由操作系统调度.

P:处理器.(Go定义的一个概念.不是指CPU).它包含运行Go代码的必要资源.也有调度

goroutine的能力.

G:Go协程.每个go关键字都会创建一个协程.

M必须持有P才可以执行代码.跟系统中的其他线程一样.M也会被系统调用阻塞.P的

个数在启动时决定.默认情况下等同于CPU核数.可以使用环境变量GOMAXPROCS

或在程序中使用runtime.GOMAXPROCS()方法指定个数.

注:M的个数一般大于P的个数.因为除了运行GO代码.runtime包还有其他内置任务

需要处理.

模型图如下:

2.3调度策略:

1).队列轮转:

每个处理器P维护着一个协程G队列.处理器P依次将协程G调度到M中执行.

协程G执行结束后.处理器P会再次调度一个G到M中执行.同时每个P会周期性的查看

全局队列中是否有待运行并将其调度到M中执行.全局队列中的G主要来自系统调用恢

复的G.P周期性的查看全局队列也是为了防止全局队列中的G长时间得不到调度会

而"饿死".

2).系统调用:

当线程在执行系统调用时.可能会被阻塞.对应到调度器模型.如果一个协程发起系统调

用.那么对应的工作线程会被阻塞.然后处理器的P中的G队列中的协程得不到调度就会

全部阻塞.

注:一般情况下.M的个数会略大于P的个数.多出来的M将会在G产生系统调用的时候

发挥作用.和线程池类似.Go也提供了一个M的池子.需要时从池子取.用完返回.不够创

建.

3).工作量切取:

通过go关键字创建的协程通常会优先放到当前协程对应的处理器中.可能有些协程自

身不断的产生新的协程.有些协程不会派生新的协程.多个P中维护的G可能是不均衡

的.不加以控制有的P很繁忙.有得会怠工.

为了解决这个问题.提供了工作量窃取策略.当某个处理器P没有需要调度的协程时.从

其他处理器偷取协程.

4).抢占式调度:

避免某个线程长时间运行.阻碍其他协程被调度的机制.

调度器会监控每个协程的执行时间.一旦执行时间过长或者有其他协程在等待时.会把

协程暂停.转而调度等待的协程.达到类似于时间片转轮的效果.

念念不忘.会有回响.

如果大家喜欢我的分享的话.可以关注我的微信公众号

念何架构之路

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 23:31:28

AgentCPM-Explore开源,4B 参数突破端侧智能体模型性能壁垒

当全行业还在争论 30B 能否挑战万亿参数时,我们给出了一个更激进的答案: 4B。没有万亿参数的算力堆砌,没有百万级数据的暴力灌入,清华大学自然语言处理实验室、中国人民大学、面壁智能与 OpenBMB 开源社区联合研发的 AgentCPM-Exp…

作者头像 李华
网站建设 2026/9/2 18:54:01

Proteus使用教程:I2C器件仿真实现指南

在Proteus中玩转I2C仿真:从协议到实战的完整指南你有没有过这样的经历?焊好板子上电,发现EEPROM写不进去数据;调试半天才发现是地址接错了,或者忘了加上拉电阻。更糟的是,IC总线“锁死”,SCL/SD…

作者头像 李华
网站建设 2026/9/2 21:52:22

STM32CubeMX时钟树配置实现工业编码器同步控制示例

用STM32CubeMX构建高精度编码器同步控制系统:从时钟树到闭环控制的实战解析你有没有遇到过这样的问题——明明PID参数调得再好,电机运行却总有点“抖”?位置反馈忽快忽慢,低速时还能接受,一提速就丢步、振荡&#xff0…

作者头像 李华
网站建设 2026/9/2 21:01:09

图解说明Keil MDK中ARM Compiler 5.06的编译输出流程

深入Keil MDK的构建心脏:图解ARM Compiler 5.06编译全过程你有没有遇到过这样的情况?代码明明编译通过了,下载到板子上却“一上电就进HardFault”;或者发现RAM莫名其妙溢出,查来查去才发现是printf偷偷引入了浮点库&am…

作者头像 李华
网站建设 2026/9/2 21:03:45

重庆思庄技术分享——如何在Linux中使用nohup命令记录日志

如何在Linux中使用nohup命令记录日志 在 Linux 中,nohup 命令用于在不挂断终端会话的情况下运行程序。默认情况下,nohup 会将输出重定向到名为 nohup.out 的文件中。如果你想自定义日志文件的名称和位置,可以按照以下步骤操作: 1、…

作者头像 李华