Go Goroutine
发布时间 - 2025-07-16 00:00:00 点击率:次大家好,我是行舟,今天我们将一起探讨go语言中的goroutine。
Goroutine是Go语言中的协程概念,是Go语言实现并发编程的主要方式。要全面理解Goroutine,我们需要从操作系统的进程和线程开始讲起。
Goroutine的概念大家可能已经有所了解,操作系统中存在进程和线程的概念。
进程是操作系统分配资源的最小单位。创建一个进程需要为其分配独立的存储空间和CPU。进程对CPU的使用是分时间片进行的。线程是进程的子任务,是操作系统的最小调度单位。创建一个线程只需要少量内存和寄存器组。同一进程内的线程可以共享内存。
相比之下,创建和销毁进程的开销远大于线程。创建一个进程通常需要数G的虚拟内存,而创建一个线程只需几M就足够了。进程切换的开销也比线程大得多,因为进程切换涉及CPU环境的保存,而线程只需保存当前调用栈和少量寄存器内容。进程和线程的通信方式不同,尽管进程可以通过共享内存进行通信,但这种方式与同一进程内线程之间的通信效率相近。多进程系统更适合高密度计算的场景,而多线程系统则更适合高I/O操作的场景。尽管操作系统已经提供了进程和线程,但Go语言的开发者认为线程的创建和切换成本仍然过高,因此在语言层面上引入了一种新的封装,即Go协程。创建一个协程仅需几KB的内存,协程的上下文切换成本更低,只需要切换更少的堆栈信息和寄存器信息。Go语言中有一个调度模型来决定哪个操作系统线程来实际执行协程。总结来说,Go协程、线程和进程的关系如下图所示。
Go语言为Goroutine的使用提供了非常简便的方法。以下是示例1:
func helloWorld() { println("Hello World")}
func main() { go helloWorld() // 启动一个协程执行helloWorld方法}在上述示例中,go helloWorld()使用go关键字后面跟随要执行的方法,即可创建一个Goroutine并执行指定的方法。
然而,运行上述代码时我们会发现没有任何输出。这是为什么呢?这是因为:启动一个Goroutine后,它会立即执行,而我们的代码也会继续执行,不会等待Goroutine返回。因此,在这个例子中,执行go helloWorld()后,不等打印出Hello World,代码就继续往下执行了。执行到下一句没有代码时,main方法就会退出。实际上,main方法在Go语言中也是一个Goroutine,只不过它比较特殊,如果main Goroutine终止执行,整个Go语言进程也会退出,其他Goroutine也会被终止。因此,在这个例子中,Hello World还没有来得及被打印,main Goroutine就已经终止了,我们也就看不到任何输出。
以下是示例2:
func helloWorld() { println("Hello World")}
func main() { go helloWorld() // 启动一个协程执行helloWorld方法 time.Sleep(100*time.Millisecond)}我们修改了示例1中的代码,在main函数退出之前进行100ms的等待。再次运行代码,我们会看到控制台输出Hello World,并在约100ms后程序退出。
在示例2中,我们已经成功输出了Hello World字符串,但我们是通过让程序等待100ms的方式来完成字符串输出的。在实际开发过程中,我们无法确定自己编写的程序需要多久才能执行完成,更多时候需要程序在执行完成后自动执行下一个动作。我们可以借助Go语言sync包中的WaitGroup来实现这种效果。以下是示例3:
func helloWorld(wg *sync.WaitGroup) { println("Hello World") defer wg.Done()}
func main() { var wg sync.WaitGroup wg.Add(1) go helloWorld(&wg) // 启动一个协程执行helloWorld方法 wg.Wait() // 阻塞执行直到接收到done信号}我们声明了wg并调用Add方法加1,当代码执行到wg.Wait()时会阻塞等待,helloWorld方法中执行wg.Done()方法后,解除wg.Wait()的阻塞,代码继续执行。
接下来看下面的例子。以下是示例4:
func main() { var wg sync.WaitGroup for i:=0; i<10; i++ { wg.Add(1) go func() { println(i) defer wg.Done() }() } wg.Wait()}运行上述代码时,我们会发现控制台并不能输出0-9的所有值,而且每次调用输出的值都不确定。这是因为go关键字后面的匿名函数是在Goroutine中执行的,不会阻塞for循环的执行。那如果我们想输出0-9全部数字,该怎么办呢?以下是示例5:
func main() { var wg sync.WaitGroup for i:=0; i<10; i++ { wg.Add(1) go func(i int) { println(i) defer wg.Done() }(i) } wg.Wait()}我们将i作为参数传递给匿名函数,因为Go语言中的函数传值都是值传递,所以0-9十个数字都会被打印出来。
尽管Goroutine使用起来非常简便,但在使用时我们还是需要谨慎,以免造成Goroutine泄漏。如果我们创建了一个Goroutine,但由于某些原因导致它永远不会退出,那么为此Goroutine分配的内存就永远不会释放,我们称这种情况为Goroutine泄漏。要防止Goroutine泄漏,我们在创建Goroutine时必须考虑它何时退出。以下是示例6:
func leak() { c := make(chan int) go func() { val := <-c println(val) }()}如上示例,我们定义了一个名为leak的方法,调用leak方法时会创建一个Goroutine。此Goroutine内部接收通道c的值,当接收到c的值后,读取并完成打印。因为没有代码为c通道传递值,所以每次调用leak方法都会生成一个永远无法结束的Goroutine。这就是非常简单的Goroutine泄漏。
接下来,我们借鉴网上的一个例子。以下是示例7:
func search(term string) (string, error) { // 模拟一段查询逻
辑 time.Sleep(200 * time.Millisecond) return "some value", nil}
// 查询结果
type result struct { record string err error}
// 100ms之后自动退出的一段代码。
func process(term string) error { // 通过Context包,实现100ms自动退出逻辑 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() // 创建一个无缓冲的通道,返回执行结果 ch := make(chan result) go func() { record, err := search(term) ch <- result{record, err} }() select { case res := <-ch: println(res.record) return res.err case <-ctx.Done(): return errors.New("process timed out") }}
func main() { process("some term")}如上示例,search方法代表耗时一段时间执行查询逻辑,result是查询返回的结果。在process方法中,通过Context包实现100ms限时返回的逻辑。然后定义无缓冲通道ch,启动Goroutine执行search方法并通过channel返回结果。select阻塞代码,直到ch通道有结果时返回结果或者等到100ms执行ctx.Done()退出函数。
如上面我们构造的例子中,search方法需要200ms才能返回结果,所以process在100ms时就退出执行了,此时接收通道异常停止继续接收数据,就会造成发送方阻塞,process中启动的Goroutine则无法回收,就产生了Goroutine泄漏。我们执行main方法,发现最终Goroutine的数量是2。
解决泄漏最简单的办法就是修改ch通道为缓冲区为1的通道,我们修改ch := make(chan result,1),此时Goroutine通过将结果值放入通道完成发送操作后返回。再次执行代码,发现Goroutine的数量是1。
Go语言的调度器内容较多,建议大家阅读这篇文章:https://www./link/87acc42a82fb7ace361a4c390d047f74
总结本文主要介绍了Go语言选择Goroutine的原因、Goroutine的基本用法和注意事项。如果大家对文章内容有任何疑问或建议,欢迎私信交流。
# golang
# 操作系统
# go语言
# ai
# 为什么
# for
# 封装
# select
# 字符串
# 循环
# 栈
# 堆
# 线程
# 多线程
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Win11怎么开启自动HDR画质_Windows11显示设置HDR选项
Python函数文档自动校验_规范解析【教程】
猎豹浏览器开发者工具怎么打开 猎豹浏览器F12调试工具使用【前端必备】
怎么用AI帮你为初创公司进行市场定位分析?
Laravel如何发送邮件和通知_Laravel邮件与通知系统发送步骤
linux写shell需要注意的问题(必看)
Java类加载基本过程详细介绍
Laravel如何实现全文搜索功能?(Scout和Algolia示例)
Laravel软删除怎么实现_Laravel Eloquent SoftDeletes功能使用教程
Laravel如何处理CORS跨域请求?(配置示例)
如何用PHP工具快速搭建高效网站?
Java垃圾回收器的方法和原理总结
Android Socket接口实现即时通讯实例代码
iOS验证手机号的正则表达式
Laravel如何实现事件和监听器?(Event & Listener实战)
大同网页,大同瑞慈医院官网?
iOS正则表达式验证手机号、邮箱、身份证号等
js实现点击每个li节点,都弹出其文本值及修改
laravel怎么为应用开启和关闭维护模式_laravel应用维护模式开启与关闭方法
用v-html解决Vue.js渲染中html标签不被解析的问题
Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】
高端建站如何打造兼具美学与转化的品牌官网?
深圳防火门网站制作公司,深圳中天明防火门怎么编码?
免费视频制作网站,更新又快又好的免费电影网站?
浏览器如何快速切换搜索引擎_在地址栏使用不同搜索引擎【搜索】
Laravel怎么多语言本地化设置_Laravel语言包翻译与Locale动态切换【手册】
Laravel怎么使用Collection集合方法_Laravel数组操作高级函数pluck与map【手册】
Laravel怎么设置路由分组Prefix_Laravel多级路由嵌套与命名空间隔离【步骤】
如何用AWS免费套餐快速搭建高效网站?
网站制作壁纸教程视频,电脑壁纸网站?
JS弹性运动实现方法分析
Laravel如何处理JSON字段_Eloquent原生JSON字段类型操作教程
如何在 React 中条件性地遍历数组并渲染元素
Laravel全局作用域是什么_Laravel Eloquent Global Scopes应用指南
创业网站制作流程,创业网站可靠吗?
什么是javascript作用域_全局和局部作用域有什么区别?
利用 Google AI 进行 YouTube 视频 SEO 描述优化
VIVO手机上del键无效OnKeyListener不响应的原因及解决方法
JS经典正则表达式笔试题汇总
如何快速生成橙子建站落地页链接?
Laravel如何将应用部署到生产服务器_Laravel生产环境部署流程
详解Android图表 MPAndroidChart折线图
教你用AI润色文章,让你的文字表达更专业
Python图片处理进阶教程_Pillow滤镜与图像增强
php在windows下怎么调试_phpwindows环境调试操作说明【操作】
米侠浏览器网页图片不显示怎么办 米侠图片加载修复
javascript和jQuery中的AJAX技术详解【包含AJAX各种跨域技术】
Win11怎么关闭资讯和兴趣_Windows11任务栏设置隐藏小组件
php做exe能调用系统命令吗_执行cmd指令实现方式【详解】
如何在云服务器上快速搭建个人网站?
下一篇:vue v-on监听事件详解
下一篇:vue v-on监听事件详解


辑 time.Sleep(200 * time.Millisecond) return "some value", nil}
// 查询结果
type result struct { record string err error}
// 100ms之后自动退出的一段代码。
func process(term string) error { // 通过Context包,实现100ms自动退出逻辑 ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() // 创建一个无缓冲的通道,返回执行结果 ch := make(chan result) go func() { record, err := search(term) ch <- result{record, err} }() select { case res := <-ch: println(res.record) return res.err case <-ctx.Done(): return errors.New("process timed out") }}
func main() { process("some term")}