单核下,Goroutine 并发调度的疑问

news/2025/3/27 2:13:13/文章来源:https://www.cnblogs.com/maidongdong/p/18790561

问题: 单核不就是只有一个系统线程执行多个groutine么,groutine需要排队呀.排队不就是串行化执行么

你的疑问非常好,我明白你在纠结的地方:既然单核只有一个系统线程(在 GOMAXPROCS=1 的情况下),Goroutine 看起来像是“排队”执行,那为什么还会出现并发问题?让我再深入解释一下,尽量清晰地解答你的困惑。

单核和 Goroutine 的调度

是的,在单核 CPU 上,且设置了 GOMAXPROCS=1 后,Go 运行时只会使用一个操作系统线程(称为 M,Machine)来执行所有的 Goroutine。这个线程就像一个工人,它需要处理多个 Goroutine(任务)。但这里的关键是:Goroutine 的执行并不是严格按照“一个完成后再开始下一个”的顺序,而是可以随时被切换的

想象一下这个工人(线程)在处理任务(Goroutine)时,不是把一个任务从头做到尾再换下一个,而是可能会在某个任务做到一半时停下来,去处理另一个任务,然后再回来继续之前的任务。这种切换是由 Go 运行时的调度器(Scheduler)控制的,而不是简单地“排队等候、顺序完成”。

为什么不是严格的串行化?

如果 Goroutine 是严格串行化执行的,那么确实不会有并发问题,因为每个 Goroutine 会完整地执行完它的代码(比如 i++),然后再轮到下一个。但 Go 的设计不是这样的,它追求的是轻量级并发

  • 调度器的抢占:在 Go 1.14 之后,调度器引入了抢占机制。即使一个 Goroutine 正在执行,如果它运行时间较长(比如循环或计算),调度器会强制暂停它,让其他 Goroutine 有机会运行。
  • 协作式切换:在早于 1.14 的版本中,Goroutine 会在某些特定点(如 I/O 操作、调用 runtime.Gosched())主动让出控制权。
  • 任务交错执行:即使没有抢占,调度器也会在 Goroutine 的执行过程中寻找切换点,导致多个 Goroutine 的代码交错运行。

这种交错执行意味着,即使只有一个线程,Goroutine 的代码片段仍然可能以非预期的顺序执行。这就是为什么即使在单核环境下,i++ 仍然会有问题。

用一个比喻说明

假设你是一个厨师(单核线程),需要为三个顾客(三个 Goroutine)各做一个汉堡(执行 i++)。每个汉堡的制作分为三步:拿面包、放肉饼、盖上盖子。如果你是严格串行化的,你会:

  1. 为顾客 A 做完整个汉堡(拿面包 → 放肉饼 → 盖盖子)。
  2. 再为顾客 B 做完整个汉堡。
  3. 最后为顾客 C 做。

但 Go 的调度器更像是让你在做汉堡时随意切换:

  • 你为顾客 A 拿了面包(读 i)。
  • 突然切换到顾客 B,拿了面包(读 i)。
  • 然后为顾客 A 放肉饼、盖盖子(完成 i++)。
  • 再为顾客 B 放肉饼、盖盖子(完成 i++)。

结果是,两个顾客的汉堡制作步骤交错了,如果 i++ 的值依赖于前一个操作的结果,就会出错。

回到 i++

i++ 包含“读-改-写”三个步骤。如果 Goroutine 在执行到一半时被切换:

  • Goroutine A 读取 i = 0,还没来得及写回就被暂停。
  • Goroutine B 读取 i = 0,完成 i++,写回 i = 1
  • Goroutine A 恢复,基于它之前读取的 0 计算 i = 1,写回 i = 1

即使只有一个线程,这种交错执行仍然会导致数据不一致。这不是因为有多个线程并行,而是因为 Goroutine 的并发调度打破了串行化的假设。

“排队”不等于“完整执行”

你提到的“排队”,可能是指 Goroutine 在等待执行,但这里的“排队”只是说它们在调度器的队列中等待被分配时间片,而不是等待前一个 Goroutine 完全结束。调度器会动态决定何时切换 Goroutine,而不是等一个 Goroutine 完整执行完再换下一个。

单核下的实验

再看一个简单的例子:

package mainimport ("fmt""runtime""sync"
)func main() {runtime.GOMAXPROCS(1) // 强制单线程var i intvar wg sync.WaitGroupwg.Add(2)go func() {defer wg.Done()i++ // 读-改-写fmt.Println("Goroutine 1:", i)}()go func() {defer wg.Done()i++ // 读-改-写fmt.Println("Goroutine 2:", i)}()wg.Wait()fmt.Println("Final i:", i)
}

运行多次,你可能会看到:

Goroutine 1: 1
Goroutine 2: 1
Final i: 1

或者:

Goroutine 2: 1
Goroutine 1: 2
Final i: 2

这说明即使在单核单线程下,i++ 的执行顺序是不确定的,因为 Goroutine 的切换导致了竞态条件。

如何实现严格串行化?

如果你真的想要严格的串行化,可以不用 go 关键字,或者用一个主 Goroutine 按顺序调用:

package mainimport "fmt"func main() {var i inti++fmt.Println("Step 1:", i)i++fmt.Println("Step 2:", i)fmt.Println("Final i:", i)
}

这样输出一定是 1, 2, 2,因为没有并发。但这就失去了 Go 的并发优势。

总结

  • 单核环境下,GOMAXPROCS=1 确实只有一个系统线程,但 Goroutine 是并发调度的,不是严格排队等前一个完成。
  • “排队”是指 Goroutine 在等待时间片,而不是等待前一个 Goroutine 完整执行。
  • i++ 的非原子性加上 Goroutine 的交错执行,导致即使单核也会出现数据不一致。
  • 要避免问题,仍然需要用 sync.Mutexsync/atomic

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/905251.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何寻找替代FTP传输文件的软件,解决文件传输难题?

FTP因其操作简单、客户端种类多、价格低廉(甚至免费)等优势,受到众多企业的青睐,在全世界范围内得到广泛的应用。但它也面临着一些安全和效率方面的挑战,因此企业都在寻找可平滑替代FTP传输文件的软件。有以下局限性: 1.安全性弱:明文传输,漏洞频发,容易遭到DOS攻击;…

20244205 《Python程序设计》实验一报告

课程:《Python程序设计》 班级: 2442 姓名: 高赫 学号:20244205 实验教师:王志强 实验日期:2025年3月24日 必修/选修: 公选课 1.实验内容 (1).熟悉Python开发环境; (2).练习Python运行、调试技能; (3).编写程序,练习变量和类型、字符串、对象、缩进和注释等; (4).编…

微积分的本质——导数.18790288

本篇为3b1b系列【微积分的本质】笔记 原视频:02-导数的悖论 | 03-用几何来求导 | 04-直观理解链式法则和乘积法则定义导数 这是一个随着时间变化,车辆行驶距离的坐标图在横轴的任何一个点\(t\)上,如果你去查看车的车速表,上面都有一个数字表示当前的车速,但这是如何计算的…

推荐8款 .NET 开源、免费、实用的 Windows 效率软件

前言 今天大姚给大家推荐8款基于 .NET 开源、免费、实用的 Windows 效率软件,开发工作提升利器,希望可以帮助到有需要的小伙伴。 DevToys DevToys是一个专门为开发者设计的Windows工具箱,完全支持离线运行,无需使用许多不真实的网站来处理你的数据,常用功能有:格式化(支…

解密prompt系列51. R1实验的一些细节讨论

DeepSeek R1出来后业界都在争相复现R1的效果,这一章我们介绍两个复现项目SimpleRL和LogicRL,还有研究模型推理能力的Cognitive Behaviour,项目在复现R1的同时还针对R1训练策略中的几个关键点进行了讨论和消融实验,包括DeepSeek R1出来后业界都在争相复现R1的效果,这一章我…

读DAMA数据管理知识体系指南30文件和内容治理

读DAMA数据管理知识体系指南30文件和内容治理1. 方法 1.1. 诉讼应诉手册1.1.1. 电子取证工作一般在发生诉讼的时候进行1.1.2. 指引应明确电子取证的目标环境,并评估当前环境和目标环境之间是否存在差距1.1.3. 应记载电子取证活动生命周期的业务流程,明确电子取证团队的角色和…

为什么springboot的jar可以直接启动

一、讲述 1.SpringBoot提供了一个插件spring-boot-maven-plugin用于把程序打包成一个可执行的jar包。 2.Spring Boot应用打包之后,生成一个Fat jar(jar包中包含jar),包含了应用依赖的jar包和Spring Boot loader相关的 类。 3.java -jar会去找jar中的manifest文件,在那里面找…

折腾笔记[17]-使用rust创建linux系统服务

使用rust内嵌配置文件, 创建新用户并创建linux的service服务;实现后台服务循环打印时间到`/tmp/log_file_service`. Use Rust to embed configuration files, create new users, and create service services for Systemd; Implement a backend service loop to print time to …

[PNPM] 其他包管理器

Yarn Yarn 这个包管理器是在 2016 的时候由 Facebook、Google、Exponent 以及 Tilde 团队共同开发推出的。当时 Yarn 的出现主要是为了解决 npm 在速度、安全性以及一致性方面的一些问题:安装速度确定性:项目A ---> 直接依赖: libraryX(1.0)-----> 间接依赖:librar…

Kioptrix Level_1

Kioptrix Level 1.1 靶场配置 导入靶场时先将vmx后缀文件中的带有ethernet0的配置行全部删除,再导入靶场,添加一个网络适配器即可 信息收集 查找目标主机ip ┌──(root㉿kali)-[~] └─# arp-scan -l Interface: eth0, type: EN10MB, MAC: 00:0c:29:84:b2:cc, IPv4: 192.168…

20244209韩仕炜《Python程序设计》实验一报告

课程:《Python程序设计》 班级: 2442 姓名:韩仕炜 实验教师:王志强 学号:20244209 实验日期:2025年3月24日 必修/选修:专选课 1. 实验内容 1.熟悉Python开发环境; 2.练习Python运行、调试技能; 3.编写程序,练习变量和类型、字符串、对象、缩进和注释等; 4.编写一…