40分钟学 Go 语言高并发:Goroutine基础与原理
Day 03 - goroutine基础与原理
1. goroutine创建和调度
1.1 goroutine基本特性
| 特性 | 说明 |
|---|---|
| 轻量级 | 初始栈大小仅2KB,可动态增长 |
| 调度方式 | 协作式调度,由Go运行时管理 |
| 创建成本 | 创建成本很低,可同时运行数十万个 |
| 通信方式 | 通过channel进行通信,而不是共享内存 |
1.2 创建goroutine的示例代码
package mainimport ("fmt""runtime""sync""time"
)// 监控goroutine数量
func monitorGoroutines(duration time.Duration, done chan struct{}) {ticker := time.NewTicker(duration)defer ticker.Stop()for {select {case <-ticker.C:fmt.Printf("当前goroutine数量: %d\n", runtime.NumGoroutine())case <-done:return}}
}// 模拟工作负载
type Worker struct {ID intwg *sync.WaitGroup
}func NewWorker(id int, wg *sync.WaitGroup) *Worker {return &Worker{ID: id,wg: wg,}
}func (w *Worker) Work(jobs <-chan int, results chan<- int) {defer w.wg.Done()for job := range jobs {fmt.Printf("Worker %d 开始处理任务 %d\n", w.ID, job)// 模拟工作负载time.Sleep(100 * time.Millisecond)results <- job * 2}
}func main() {numWorkers := 5numJobs := 10// 创建通道jobs := make(chan int, numJobs)results := make(chan int, numJobs)// 创建WaitGroup来等待所有worker完成var wg sync.WaitGroup// 监控goroutine数量done := make(chan struct{})go monitorGoroutines(time.Second, done)// 创建worker池fmt.Printf("创建 %d 个worker\n", numWorkers)for i := 1; i <= numWorkers; i++ {wg.Add(1)worker := NewWorker(i, &wg)go worker.Work(jobs, results)}// 发送任务fmt.Printf("发送 %d 个任务\n", numJobs)for j := 1; j <= numJobs; j++ {jobs <- j}close(jobs)// 等待所有worker完成go func() {wg.Wait()close(results)}()// 收集结果for result := range results {fmt.Printf("收到结果: %d\n", result)}// 停止监控done <- struct{}{}// 最终统计fmt.Printf("最终goroutine数量: %d\n", runtime.NumGoroutine())
}
2. GMP模型详解
2.1 GMP组件说明
| 组件 | 说明 | 职责 |
|---|---|---|
| G (Goroutine) | goroutine的抽象 | 包含goroutine的栈、程序计数器等信息 |
| M (Machine) | 工作线程 | 执行G的实体,对应系统线程 |
| P (Processor) | 处理器 | 维护G的运行队列,提供上下文环境 |
2.2 GMP调度流程图

2.3 GMP相关的运行时参数
runtime.GOMAXPROCS(n) // 设置最大P的数量
runtime.NumCPU() // 获取CPU核心数
runtime.NumGoroutine() // 获取当前goroutine数量
3. 并发模型原理
3.1 Go并发模型特点
| 特点 | 说明 |
|---|---|
| CSP模型 | 通过通信来共享内存,而不是共享内存来通信 |
| 非阻塞调度 | goroutine让出CPU时不会阻塞其他goroutine |
| 工作窃取 | 空闲P可以从其他P窃取任务 |
| 抢占式调度 | 支持基于信号的抢占式调度 |
3.2 并发模型示例
package mainimport ("context""fmt""runtime""sync""time"
)// Pipeline 表示一个数据处理管道
type Pipeline struct {input chan intoutput chan intdone chan struct{}
}// NewPipeline 创建新的处理管道
func NewPipeline() *Pipeline {return &Pipeline{input: make(chan int),output: make(chan int),done: make(chan struct{}),}
}// Process 处理数据
func (p *Pipeline) Process(ctx context.Context) {go func() {defer close(p.output)for {select {case num, ok := <-p.input:if !ok {return}// 模拟处理result := num * 2select {case p.output <- result:case <-ctx.Done():return}case <-ctx.Done():return}}}()
}// WorkerPool 表示工作池
type WorkerPool struct {workers inttasks chan func()wg sync.WaitGroup
}// NewWorkerPool 创建新的工作池
func NewWorkerPool(workers int) *WorkerPool {pool := &WorkerPool{workers: workers,tasks: make(chan func(), workers*2),}pool.Start()return pool
}// Start 启动工作池
func (p *WorkerPool) Start() {for i := 0; i < p.workers; i++ {p.wg.Add(1)go func(workerID int) {defer p.wg.Done()for task := range p.tasks {fmt.Printf("Worker %d executing task\n", workerID)task()}}(i + 1)}
}// Submit 提交任务
func (p *WorkerPool) Submit(task func()) {p.tasks <- task
}// Stop 停止工作池
func (p *WorkerPool) Stop() {close(p.tasks)p.wg.Wait()
}func main() {// 设置使用的CPU核心数runtime.GOMAXPROCS(runtime.NumCPU())// 创建上下文ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()// 创建处理管道pipeline := NewPipeline()pipeline.Process(ctx)// 创建工作池pool := NewWorkerPool(3)// 启动生产者go func() {defer close(pipeline.input)for i := 1; i <= 10; i++ {select {case pipeline.input <- i:fmt.Printf("Sent %d to pipeline\n", i)case <-ctx.Done():return}}}()// 使用工作池处理pipeline输出go func() {for result := range pipeline.output {result := result // 捕获变量pool.Submit(func() {// 模拟处理时间time.Sleep(100 * time.Millisecond)fmt.Printf("Processed result: %d\n", result)})}// 处理完成后停止工作池pool.Stop()}()// 等待上下文结束<-ctx.Done()fmt.Println("Main context done")
}
4. goroutine生命周期
4.1 生命周期状态
| 状态 | 说明 |
|---|---|
| 创建 | goroutine被创建,分配栈空间 |
| 可运行 | 等待被调度执行 |
| 运行中 | 正在被M执行 |
| 系统调用中 | 阻塞在系统调用上 |
| 等待中 | 因channel或同步原语阻塞 |
| 死亡 | 执行完成,等待回收 |
4.2 生命周期示例
package mainimport ("context""fmt""runtime""runtime/debug""sync""time"
)// GoroutineMonitor 用于监控goroutine的状态
type GoroutineMonitor struct {startTime time.TimeendTime time.Timestatus stringsync.Mutex
}// NewGoroutineMonitor 创建新的goroutine监控器
func NewGoroutineMonitor() *GoroutineMonitor {return &GoroutineMonitor{startTime: time.Now(),status: "created",}
}// UpdateStatus 更新goroutine状态
func (g *GoroutineMonitor) UpdateStatus(status string) {g.Lock()defer g.Unlock()g.status = statusfmt.Printf("Goroutine状态更新: %s, 时间: %v\n", status, time.Since(g.startTime))
}// Complete 标记goroutine完成
func (g *GoroutineMonitor) Complete() {g.Lock()defer g.Unlock()g.endTime = time.Now()g.status = "completed"fmt.Printf("Goroutine完成, 总运行时间: %v\n", g.endTime.Sub(g.startTime))
}// Task 代表一个任务
type Task struct {ID intDuration time.DurationMonitor *GoroutineMonitor
}// Execute 执行任务
func (t *Task) Execute(ctx context.Context, wg *sync.WaitGroup) {defer wg.Done()defer t.Monitor.Complete()defer func() {if r := recover(); r != nil {fmt.Printf("Task %d panic: %v\nStack: %s\n", t.ID, r, debug.Stack())t.Monitor.UpdateStatus("panic")}}()t.Monitor.UpdateStatus("running")// 模拟任务执行select {case <-time.After(t.Duration):t.Monitor.UpdateStatus("normal completion")case <-ctx.Done():t.Monitor.UpdateStatus("cancelled")return}// 模拟一些可能的状态if t.ID%4 == 0 {t.Monitor.UpdateStatus("blocked")time.Sleep(100 * time.Millisecond)} else if t.ID%3 == 0 {panic("模拟任务panic")}
}// TaskScheduler 任务调度器
type TaskScheduler struct {tasks chan Taskworkers intmonitors map[int]*GoroutineMonitormu sync.RWMutex
}// NewTaskScheduler 创建任务调度器
func NewTaskScheduler(workers int) *TaskScheduler {return &TaskScheduler{tasks: make(chan Task, workers*2),workers: workers,monitors: make(map[int]*GoroutineMonitor),}
}// AddTask 添加任务
func (s *TaskScheduler) AddTask(task Task) {s.mu.Lock()s.monitors[task.ID] = task.Monitors.mu.Unlock()s.tasks <- task
}// Start 启动调度器
func (s *TaskScheduler) Start(ctx context.Context) {var wg sync.WaitGroup// 启动worker池for i := 0; i < s.workers; i++ {wg.Add(1)go func(workerID int) {defer wg.Done()for task := range s.tasks {task.Execute(ctx, &wg)}}(i)}go func() {wg.Wait()close(s.tasks)}()
}func main() {// 设置最大P的数量runtime.GOMAXPROCS(4)ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()// 创建调度器scheduler := NewTaskScheduler(3)// 启动调度器scheduler.Start(ctx)// 创建多个任务for i := 1; i <= 10; i++ {task := Task{ID: i,Duration: time.Duration(i*200) * time.Millisecond,Monitor: NewGoroutineMonitor(),}scheduler.AddTask(task)}// 等待context结束<-ctx.Done()// 打印最终状态fmt.Println("\n最终状态:")scheduler.mu.RLock()for id, monitor := range scheduler.monitors {monitor.Lock()fmt.Printf("Task %d - 状态: %s\n", id, monitor.status)monitor.Unlock()}scheduler.mu.RUnlock()
}
4.3 Goroutine生命周期状态转换图

5. 实践注意事项
5.1 goroutine泄露的常见场景
- channel阻塞且无法释放
func leakyGoroutine() {ch := make(chan int) // 无缓冲channelgo func() {val := <-ch // 永远阻塞在这里}()// ch没有被写入,goroutine泄露
}
- 无限循环
func infiniteLoop() {go func() {for {// 没有退出条件的循环// 应该添加 select 或 检查退出信号}}()
}
5.2 最佳实践表格
| 最佳实践 | 说明 |
|---|---|
| 合理控制goroutine数量 | 避免无限制创建goroutine |
| 使用context控制生命周期 | 优雅管理goroutine的退出 |
| 处理panic | 避免goroutine意外退出影响整个程序 |
| 及时清理资源 | 使用defer确保资源释放 |
| 合理设置GOMAXPROCS | 根据CPU核心数调整P的数量 |
5.3 性能优化建议
- goroutine池化
type Pool struct {work chan func()sem chan struct{}
}func NewPool(size int) *Pool {return &Pool{work: make(chan func()),sem: make(chan struct{}, size),}
}func (p *Pool) Submit(task func()) {select {case p.work <- task:case p.sem <- struct{}{}:go p.worker(task)}
}func (p *Pool) worker(task func()) {defer func() { <-p.sem }()for {task()task = <-p.work}
}
- 避免锁竞争
// 使用atomic替代mutex
type Counter struct {count int32
}func (c *Counter) Increment() {atomic.AddInt32(&c.count, 1)
}func (c *Counter) Get() int32 {return atomic.LoadInt32(&c.count)
}
6. 调试和监控
6.1 调试工具
- GODEBUG参数
GODEBUG=schedtrace=1000 ./program # 每1000ms输出调度信息
GODEBUG=gctrace=1 ./program # 输出GC信息
- pprof工具
import _ "net/http/pprof"go func() {log.Println(http.ListenAndServe("localhost:6060", nil))
}()
6.2 监控指标
- goroutine数量
- P的使用率
- 系统调用次数
- 调度延迟
- GC影响
通过深入理解goroutine的原理和生命周期,我们可以:
- 更好地控制并发程序的行为
- 避免常见的并发陷阱
- 优化程序性能
- 排查并发相关问题
怎么样今天的内容还满意吗?再次感谢观众老爷的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!
相关文章:
40分钟学 Go 语言高并发:Goroutine基础与原理
Day 03 - goroutine基础与原理 1. goroutine创建和调度 1.1 goroutine基本特性 特性说明轻量级初始栈大小仅2KB,可动态增长调度方式协作式调度,由Go运行时管理创建成本创建成本很低,可同时运行数十万个通信方式通过channel进行通信&#x…...
Figma插件指南:12款提升设计生产力的插件
在当今的设计领域,Figma已经成为许多UI设计师和团队的首选原型和数字设计软件。随着Figma的不断更新和插件库的扩展,这些工具极大地提升了设计工作的效率。本文将介绍12款实用的Figma插件,帮助你在UI设计中更加高效。 即时AI 即时AI利用先进…...
【K8S系列】Kubernetes集群资源管理与调度 深度分析
在现代微服务架构中,Kubernetes(K8s)作为容器编排平台,提供了强大的资源管理和调度能力。然而,随着应用规模的扩大和复杂性增加,如何高效地管理和调度集群资源成为一个关键挑战。本文将深入探讨 Kubernetes…...
delphi fmx android 离线人脸识别
搜遍全网都没有找到delphi android 能用的 离线人脸识别,无需注册什么开发者 有这方面需求的可以用fsdk 这边用的luxand.FSDK8.0 android下的注册号要自己找下 1,用老猫的工具将android 下的sdk,FSDK.java 编译成FSDK.jar 老猫的工具 2,用上面的工具将FSDK.jar 生成de…...
Linux mountpoint 命令详解
前言 在 Linux 系统中,文件系统管理是一个非常重要的任务。mountpoint 是一个常用的小工具,用于检查目录是否是挂载点。本篇博客将详细介绍 mountpoint 命令的用法及其在日常系统管理中的应用。 什么是挂载点? 挂载点是一个目录࿰…...
Linux驱动开发(9):pinctrl子系统和gpio子系统--led实验
在前面章节,我们有过使用寄存器去编写字符设备的经历了。这种直接在驱动代码中, 通过寄存器映射来对外设进行使用的编程方式,从驱动开发者的角度可以说是灾难。 因为每当芯片的寄存器发生了改动,那么底层的驱动几乎得重写。 那么…...
用sqlmap工具打sqli-labs前20关靶场
这个星期我们用手动注入打了前20关靶场,今天我们用sqlmap直接梭哈前20关 1.介绍sqlmap sqlmap是一个自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL和SQL注入漏洞。 2.下载和使用sqlmap 官方下载地址:GitHub - sq…...
代码随想录算法训练营第二十一天 | 93.复原IP地址 | 78.子集
Day 20 总结 自己实现中遇到哪些困难 一句话讲明白问题分类 组合问题和分割问题都是收集树的叶子节点,子集问题是找树的所有节点!切割字符串问题回顾 昨天的切割回文子串,和今天的切割ip地址,都是需要将字符串拆分成 n 份。只不过…...
#Uniapp篇:支持纯血鸿蒙发布适配UIUI
uni-ui梳理 组件生命周期 https://uniapp.dcloud.net.cn/tutorial/page.html#componentlifecycle 页面生命周期 https://uniapp.dcloud.net.cn/collocation/App.html#applifecycle onLaunch 当uni-app 初始化完成时触发(全局只触发一次),…...
边缘提取函数 [OPENCV--2]
OPENCV中最常用的边界检测是CANNY函数 下面展示它的用法 通常输入一个灰度图像(边界一般和颜色无关)这样也可以简化运算cv::Canny(inmat , outmat , therhold1, therhold2 ) 第一个参数是输入的灰度图像,第二个是输出的图像这两个参数都是引用…...
插值原理(数值计算方法)
插值原理(数值计算方法) 一. 原理介绍二. 图例三. 唯一性表述 一. 原理介绍 在数学中,插值(Interpolation)是指通过已知的离散数据点,构造一个连续的函数,该函数能够精确地通过这些数据点&#…...
【Pikachu】SSRF(Server-Side Request Forgery)服务器端请求伪造实战
尽人事以听天命 1.Server-Side Request Forgery服务器端请求伪造学习 SSRF(服务器端请求伪造)攻击的详细解析与防范 SSRF(Server-Side Request Forgery,服务器端请求伪造) 是一种安全漏洞,它允许攻击者通…...
IDEA怎么定位java类所用maven依赖版本及引用位置
在实际开发中,我们可能会遇到需要搞清楚代码所用依赖版本号及引用位置的场景,便于排查问题,怎么通过IDEA实现呢? 可以在IDEA中打开项目,右键点击maven的pom.xml文件,或者在maven窗口下选中项目,…...
Discuz论坛网站管理员的默认用户名admin怎么修改啊?
当我们在某个论坛注册账号后,处于某种原因想要修改用户名,该如何修改? Discuz论坛网站管理员处于安全性或某种原因想要修改默认用户名admin该如何修改?驰网飞飞和你分享 其实非常简单,但是普通用户没有修改权限&…...
BIO、NIO、AIO的区别?
文章目录 BIO、NIO、AIO的区别?为什么不使用java 原生nio哪些项目使用了netty BIO阻塞I/O存在问题 NIO(nonblocking IO)Java NIO channel(通道)、buffer、selector(选择器) AIO(Asynchronous I/O) BIO、NIO…...
音视频入门基础:MPEG2-TS专题(7)——FFmpeg源码中,读取出一个transport packet数据的实现
一、引言 从《音视频入门基础:MPEG2-TS专题(3)——TS Header简介》可以知道,TS格式有三种:分别为transport packet长度固定为188、192和204字节。而FFmpeg源码中是通过read_packet函数从一段MPEG2-TS传输流/TS文件中读…...
Flutter中sqflite的使用案例
目录 引言 安装sqflite 创建表 查询数据 添加数据 删除数据 更新数据 完整使用案例 引言 随着移动应用的发展,本地数据存储成为了一个不可或缺的功能。在Flutter中,sqflite 是一个非常流行且强大的SQLite插件,它允许开发者在移动设备…...
【2024 Optimal Control 16-745】【Lecture 2】integrators.ipynb功能分析
代码功能分析 导入库和项目设置 import Pkg; Pkg.activate(__DIR__); Pkg.instantiate()功能:激活当前文件夹为 Julia 项目环境,并安装当前项目中缺失的依赖包。 import Pkg: 导入 Julia 的包管理模块 Pkg,用于管理项目依赖。 …...
【linux】ubuntu下常用快捷键【笔记】
环境 硬件:通用PC 系统:Ubuntu 20.04 软件 : 打开终端窗口:Ctrl Alt T 关闭当前窗口:Alt F4 改变窗口大小:Alt F8 移动窗口: Alt F7 配合 “←”、“→”、“↑”、“↓”来移动窗口 …...
【Linux】常用命令练习
一、常用命令 1、在/hadoop目录下创建src和WebRoot两个文件夹 分别创建:mkdir -p /hadoop/src mkdir -p /hadoop/WebRoot 同时创建:mkdir -p /hadoop/{src,WebRoot}2、进入到/hadoop目录,在该目录下创建.classpath和README文件 分别创建&am…...
【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...
python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)
更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
【Nginx】使用 Nginx+Lua 实现基于 IP 的访问频率限制
使用 NginxLua 实现基于 IP 的访问频率限制 在高并发场景下,限制某个 IP 的访问频率是非常重要的,可以有效防止恶意攻击或错误配置导致的服务宕机。以下是一个详细的实现方案,使用 Nginx 和 Lua 脚本结合 Redis 来实现基于 IP 的访问频率限制…...
stm32wle5 lpuart DMA数据不接收
配置波特率9600时,需要使用外部低速晶振...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
热烈祝贺埃文科技正式加入可信数据空间发展联盟
2025年4月29日,在福州举办的第八届数字中国建设峰会“可信数据空间分论坛”上,可信数据空间发展联盟正式宣告成立。国家数据局党组书记、局长刘烈宏出席并致辞,强调该联盟是推进全国一体化数据市场建设的关键抓手。 郑州埃文科技有限公司&am…...
向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...
ubuntu中安装conda的后遗症
缘由: 在编译rk3588的sdk时,遇到编译buildroot失败,提示如下: 提示缺失expect,但是实测相关工具是在的,如下显示: 然后查找借助各个ai工具,重新安装相关的工具,依然无解。 解决&am…...
