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…...
力扣-Hot100-数组【算法学习day.37】
前言 ###我做这类文档一个重要的目的还是给正在学习的大家提供方向(例如想要掌握基础用法,该刷哪些题?)我的解析也不会做的非常详细,只会提供思路和一些关键点,力扣上的大佬们的题解质量是非常非常高滴&am…...
表格不同类型的数据如何向量化?
在进行机器学习项目时,首先需要获取数据,这些数据可以来自数据库、API、网络抓取,或从CSV、Excel等文件中读取。数据可能包含数值、文本和类别等多种特征,但原始数据通常无法直接用于训练模型。 数据预处理包括清洗、填补缺失值和…...
成都栩熙酷,电商服务新选择
在当今数字经济蓬勃发展的时代,电商平台已成为推动商业创新、促进消费升级的重要力量。抖音小店,作为短视频与电商深度融合的产物,凭借其独特的社交属性和内容营销优势,迅速吸引了大量用户和商家的关注。在这场变革中,…...
【java基础】微服务篇
参考黑马八股视频。 目录 Spring Cloud 5大组件 注册中心 负载均衡 限流 CAP和BASE 分布式事务解决方案 分布式服务的接口幂等性 分布式任务调度 Spring Cloud 5大组件 注册中心 Eureka的作用 健康监控 负载均衡 限流 漏桶固定速率,令牌桶不限速 CAP和BA…...
【LLM训练系列02】如何找到一个大模型Lora的target_modules
方法1:观察attention中的线性层 import numpy as np import pandas as pd from peft import PeftModel import torch import torch.nn.functional as F from torch import Tensor from transformers import AutoTokenizer, AutoModel, BitsAndBytesConfig from typ…...
uni-app快速入门(八)--常用内置组件(上)
uni-app提供了一套基础组件,类似HTML里的标签元素,不推荐在uni-app中使用使用div等HTML标签。在uni-app中,对应<div>的标签是view,对应<span>的是text,对应<a>的是navigator,常用uni-app…...
基于Amazon Bedrock:一站式多模态数据处理新体验
目录 引言 关于Amazon Bedrock 基础模型体验 1、进入环境 2、发现模型及快速体验 3、打开 Amazon Bedrock 控制台 4、通过 Playgrounds 体验模型 (1)文本生成 (2)图片生成 关于资源清理 结束语 引言 在云计算和人工智能…...
FAX动作文件优化脚本(MAX清理多余关键帧插件)
大较好,为大家介绍一个节省FBX容量的插件!只保留有用的动画轴向,其他不参与动画运动的清除! 一.插件目的:: 1.我们使用的U3D引擎产生的游戏资源包容量太大,故全方位优化动画资源; 2.在max曲线编辑器内,点取轴向太过麻烦,费事,直观清除帧大大提高效率。 如: 二:…...
Chapter 2 - 16. Understanding Congestion in Fibre Channel Fabrics
Transforming an I/O Operation to FC frames A read or write I/O operation (Figure 2-28) between an initiator and a target undergoes a series of transformations before being transmitted on a Fibre Channel link. 启动程序和目标程序之间的读取或写入 I/O 操作(图…...
mysql数据库(六)pymysql、视图、触发器、存储过程、函数、流程控制、数据库连接池
pymysql、视图、触发器、存储过程、函数、流程控制、数据库连接池 文章目录 pymysql、视图、触发器、存储过程、函数、流程控制、数据库连接池一、pymysql二、视图三、触发器四、存储过程五、函数六、流程控制七、数据库连接池 一、pymysql 可以使用pip install pymysql安装py…...
微商城网站建设好么/推客平台
CGI CGI全称是“公共网关接口”(Common Gateway Interface),HTTP服务器与你的或其它机器上的程序进行“交谈”的一种工具,其程序须运行在网络服务器上。 CGI可以用任何一种语言编写,只要这种语言具有标准输入、输出和环境变量。如php,perl,tc…...
民治营销网站制作/游戏推广怎么做引流
在 SpringBoot 中默认的 404 返回的信息如下: 我们如果要对 404 返回的异常信息做重新定义,我们需要新建一个 controller 来处理它,如下: import com.asurplus.common.utils.RES; import org.springframework.boot.web.servlet.…...
主页去掉wordpress/外链图片
本文实例讲述了jQuery插件FusionCharts绘制的3D环饼图效果。分享给大家供大家参考,具体如下:1、index.html页面:/p>"http://www.w3.org/TR/html4/loose.dtd">FusionCharts Doughnut3D$(function(){var doughnut3D new Fusio…...
深圳做网站 百度智能小程序/宁波优化网站哪家好
2019独角兽企业重金招聘Python工程师标准>>> 虹软人脸识别SDK之Java版,支持SDK 1.1,以及当前最新版本2.0,滴滴,抓紧上车! JDK SDK Win release license status 前言 由于业务需求,最近跟人脸识别…...
安远做网站/seo综合查询怎么关闭
在 OS X El Capitan 中有一个跟安全相关的模式叫 SIP(System Integrity Protection ) ,它禁止让软件以 root 身份来在 mac 上运行,并且对于目录 /System 、/sbin、/usr(不包含/usr/local/) 仅仅供系统使用&…...
做一个公司网站多少钱/武汉服装seo整站优化方案
打开goods.php 在$smarty->assign(goods, $goods);后面添加$smarty->assign(brand_list, get_brands());转载于:https://www.cnblogs.com/wpindesign/p/3664111.html...