深入浅出 Go 语言:协程(Goroutine)详解
深入浅出 Go 语言:协程(Goroutine)详解
引言
Go 语言的协程(goroutine)是其并发模型的核心特性之一。协程允许你轻松地编写并发代码,而不需要复杂的线程管理和锁机制。通过协程,你可以同时执行多个任务,并且这些任务可以共享相同的地址空间,从而简化了内存管理和数据共享。
本文将深入浅出地介绍 Go 语言中的协程编程,涵盖协程的基本概念、如何启动和管理协程、通道(channel)的使用以及常见的并发模式。
1. 协程的基本概念
1.1 什么是协程?
协程是一种轻量级的线程,它由 Go 运行时自动调度和管理。与传统的操作系统线程不同,协程的创建和切换开销非常小,因此可以在一个程序中创建成千上万个协程,而不会对性能造成显著影响。
在 Go 中,协程通过 go 关键字启动。任何函数都可以作为协程运行,只需在其调用前加上 go 关键字即可。
1.1.1 启动协程
启动协程的基本语法如下:
go 函数名(参数列表)
例如,启动一个简单的协程:
func sayHello() {fmt.Println("Hello, World!")
}func main() {go sayHello()time.Sleep(time.Second) // 确保主程序等待协程完成
}
在这个例子中,sayHello 函数作为一个协程启动。由于协程是异步执行的,主程序可能会在协程完成之前结束。为了确保协程有足够的时间执行,我们在主程序中添加了一个 time.Sleep,以等待一段时间。
1.2 协程的特点
- 轻量级:协程的创建和切换开销非常小,可以在一个程序中创建大量协程。
- 自动调度:协程由 Go 运行时自动调度,开发者不需要手动管理线程的创建和销毁。
- 共享内存:协程之间可以共享相同的地址空间,简化了内存管理和数据共享。
- 非阻塞:协程之间的通信和同步是非阻塞的,避免了传统线程中的锁竞争问题。
1.3 协程与线程的区别
- 线程:由操作系统管理,创建和切换开销较大,适用于需要高性能和复杂调度的场景。
- 协程:由 Go 运行时管理,创建和切换开销较小,适用于高并发场景,尤其是 I/O 密集型任务。
2. 协程的管理
2.1 协程的生命周期
协程的生命周期由 Go 运行时自动管理,开发者不需要显式地创建或销毁协程。然而,在某些情况下,我们仍然需要控制协程的执行,以确保程序的正确性和性能。
2.1.1 使用 WaitGroup 等待协程完成
在多协程场景中,主程序通常需要等待所有协程完成后再退出。sync.WaitGroup 是 Go 提供的一个工具,用于等待一组协程完成。
简单示例
以下是一个使用 WaitGroup 等待协程完成的示例:
package mainimport ("fmt""sync""time"
)func worker(id int, wg *sync.WaitGroup) {defer wg.Done() // 在函数返回时调用 Donefmt.Printf("Worker %d starting
", id)time.Sleep(time.Second)fmt.Printf("Worker %d done
", id)
}func main() {var wg sync.WaitGroupfor i := 1; i <= 5; i++ {wg.Add(1) // 每启动一个协程,增加计数器go worker(i, &wg)}wg.Wait() // 等待所有协程完成fmt.Println("All workers done")
}
在这个例子中,WaitGroup 用于跟踪启动的协程数量,并在所有协程完成后通知主程序。wg.Add(1) 用于增加计数器,wg.Done() 用于减少计数器,wg.Wait() 用于阻塞主程序,直到所有协程完成。
2.1.2 使用 context 控制协程的取消
在某些情况下,我们可能需要提前取消协程的执行。context 包提供了上下文管理功能,允许你在协程之间传递取消信号。
简单示例
以下是一个使用 context 控制协程取消的示例:
package mainimport ("context""fmt""time"
)func worker(ctx context.Context, id int) {for {select {case <-ctx.Done():fmt.Printf("Worker %d canceled
", id)returndefault:fmt.Printf("Worker %d working
", id)time.Sleep(500 * time.Millisecond)}}
}func main() {ctx, cancel := context.WithCancel(context.Background())for i := 1; i <= 3; i++ {go worker(ctx, i)}time.Sleep(2 * time.Second)cancel() // 发送取消信号time.Sleep(1 * time.Second) // 确保协程有时间处理取消信号
}
在这个例子中,context.WithCancel 创建了一个带有取消功能的上下文。当调用 cancel() 时,所有监听该上下文的协程都会收到取消信号并退出。
3. 通道(Channel)
通道是 Go 语言中用于协程之间通信的机制。通过通道,协程可以安全地发送和接收数据,而不需要使用锁或其他同步原语。
3.1 通道的基本用法
创建通道的基本语法如下:
ch := make(chan 类型)
例如,创建一个整数类型的通道:
ch := make(chan int)
3.1.1 发送和接收数据
发送数据到通道的语法为 ch <- value,接收数据的语法为 value := <-ch。
简单示例
以下是一个使用通道进行协程间通信的示例:
package mainimport ("fmt"
)func send(ch chan<- int, value int) {ch <- value
}func receive(ch <-chan int) {value := <-chfmt.Println("Received:", value)
}func main() {ch := make(chan int)go send(ch, 42)receive(ch)
}
在这个例子中,send 协程向通道发送数据,receive 协程从通道接收数据。注意,通道的方向可以通过箭头符号指定,chan<- 表示只写通道,<-chan 表示只读通道。
3.2 无缓冲通道与带缓冲通道
- 无缓冲通道:默认情况下,通道是无缓冲的。发送和接收操作必须同时发生,否则会导致阻塞。
- 带缓冲通道:通过指定缓冲区大小,可以创建带缓冲的通道。发送操作不会立即阻塞,直到缓冲区满为止;接收操作也不会立即阻塞,直到缓冲区为空为止。
带缓冲通道示例
以下是一个使用带缓冲通道的示例:
package mainimport ("fmt"
)func main() {ch := make(chan int, 2) // 创建带缓冲的通道ch <- 1ch <- 2fmt.Println(<-ch) // 输出: 1fmt.Println(<-ch) // 输出: 2
}
在这个例子中,make(chan int, 2) 创建了一个容量为 2 的带缓冲通道。我们可以连续发送两个值而不阻塞,直到缓冲区满为止。
3.3 选择器(Select)
select 语句用于在多个通道操作之间进行选择。它可以监听多个通道的发送和接收操作,并根据最先准备好的操作执行相应的代码块。
简单示例
以下是一个使用 select 语句的示例:
package mainimport ("fmt""time"
)func main() {ch1 := make(chan string)ch2 := make(chan string)go func() {time.Sleep(2 * time.Second)ch1 <- "Hello from ch1"}()go func() {time.Sleep(1 * time.Second)ch2 <- "Hello from ch2"}()select {case msg1 := <-ch1:fmt.Println(msg1)case msg2 := <-ch2:fmt.Println(msg2)}
}
在这个例子中,select 语句监听了两个通道 ch1 和 ch2。由于 ch2 的协程先完成,因此 select 会优先处理 ch2 的消息。
4. 常见的并发模式
4.1 工作者池模式
工作者池模式是一种常见的并发模式,适用于需要处理大量任务的场景。通过创建一个固定数量的协程池,可以有效地复用协程,避免频繁创建和销毁协程带来的开销。
简单示例
以下是一个实现工作者池模式的示例:
package mainimport ("fmt""sync"
)type Task struct {ID intData string
}func worker(tasks <-chan Task, results chan<- string, wg *sync.WaitGroup) {defer wg.Done()for task := range tasks {result := fmt.Sprintf("Processed task %d with data: %s", task.ID, task.Data)results <- result}
}func main() {numWorkers := 3numTasks := 10tasks := make(chan Task, numTasks)results := make(chan string, numTasks)var wg sync.WaitGroup// 启动工作者协程for i := 0; i < numWorkers; i++ {wg.Add(1)go worker(tasks, results, &wg)}// 发送任务for i := 1; i <= numTasks; i++ {tasks <- Task{ID: i, Data: fmt.Sprintf("Task %d", i)}}close(tasks)// 收集结果go func() {wg.Wait()close(results)}()for result := range results {fmt.Println(result)}
}
在这个例子中,我们创建了一个包含 3 个协程的工作池,并向其发送 10 个任务。每个协程从 tasks 通道中获取任务并处理,处理结果通过 results 通道返回。sync.WaitGroup 用于等待所有协程完成。
4.2 生产者-消费者模式
生产者-消费者模式是一种经典的并发模式,适用于需要在多个协程之间共享数据的场景。生产者负责生成数据并将其放入通道,消费者负责从通道中取出数据并进行处理。
简单示例
以下是一个实现生产者-消费者模式的示例:
package mainimport ("fmt""sync"
)func producer(ch chan<- int, wg *sync.WaitGroup) {defer wg.Done()for i := 1; i <= 5; i++ {ch <- ifmt.Printf("Produced: %d
", i)}
}func consumer(ch <-chan int, wg *sync.WaitGroup) {defer wg.Done()for i := range ch {fmt.Printf("Consumed: %d
", i)}
}func main() {ch := make(chan int, 5)var wg sync.WaitGroupwg.Add(1)go producer(ch, &wg)wg.Add(1)go consumer(ch, &wg)wg.Wait()close(ch)
}
在这个例子中,producer 协程负责生成数据并将其放入通道,consumer 协程负责从通道中取出数据并进行处理。sync.WaitGroup 用于等待生产者和消费者完成。
5. 总结
通过本文的学习,你已经掌握了 Go 语言中协程编程的基本概念和使用方法。协程允许你轻松地编写并发代码,而不需要复杂的线程管理和锁机制。我们介绍了如何启动和管理协程、通道的使用以及常见的并发模式。
参考资料
- Go 官方文档 - 并发
- Go 语言中文网 - 协程
- Go 语言官方博客 - 协程

相关文章:
深入浅出 Go 语言:协程(Goroutine)详解
深入浅出 Go 语言:协程(Goroutine)详解 引言 Go 语言的协程(goroutine)是其并发模型的核心特性之一。协程允许你轻松地编写并发代码,而不需要复杂的线程管理和锁机制。通过协程,你可以同时执行多个任务,并…...
Python从0到100(八十九):Resnet、LSTM、Shufflenet、CNN四种网络分析及对比
前言: 零基础学Python:Python从0到100最新最全教程。 想做这件事情很久了,这次我更新了自己所写过的所有博客,汇集成了Python从0到100,共一百节课,帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...
实验:k8s+keepalived+nginx+iptables
1、创建两个nginx的pod,app都是nginx nginx1 nginx2 2、创建两个的pod的service 3、配置两台keepalived的调度器和nginx七层反向代理,VIP设置192.168.254.110 keepalived调度器master keepalived调度器backup 两台调度器都配置nginx七层反向代理&#…...
elpis全栈课程学习之elpis-core学习总结
elpis全栈课程学习之elpis-core学习总结 核心原理 elpis-core是全栈框架elpis的服务端内核,主要应用于服务端接口的开发以及页面的SSR渲染,elpis-core基于约定优于配置的原理,通过一系列的loader来加载对应的文件,大大节约用户的…...
LlamaFactory-webui:训练大语言模型的入门级教程
LlamaFactory是一个开源框架,支持多种流行的语言模型,及多种微调技术,同时,以友好的交互式界面,简化了大语言模型的学习。 本章内容,从如何拉取,我已经搭建好的Llamafactory镜像开始࿰…...
手机打电话时如何识别对方按下的DTMF按键的字符-安卓AI电话机器人
手机打电话时如何识别对方按下的DTMF按键的字符 --安卓AI电话机器人 一、前言 前面的篇章中,使用蓝牙电话拦截手机通话的声音,并对数据加工,这个功能出来也有一段时间了。前段时间有试用的用户咨询说:有没有办法在手机上ÿ…...
Spring Cloud LoadBalancer详解
一、介绍 Spring Cloud LoadBalancer是Spring Cloud官方自己提供的客户端负载均衡器,抽象和实现,用来替代Ribbon(已经停更), 二、Ribbon和Loadbalance 对比 组件组件提供的负载策略支持负载的客户端Ribbon随机 RandomRule轮询 RoundRobinRule 重试 RetryRule最低并发 Bes…...
使用Spring Boot与达梦数据库(DM)进行多数据源配置及MyBatis Plus集成
使用Spring Boot与达梦数据库(DM)进行多数据源配置及MyBatis Plus集成 在现代企业级应用开发中,处理多个数据源是一个常见的需求。本文将详细介绍如何使用Spring Boot结合达梦数据库(DM),并通过MyBatis Plus来简化数据库操作&…...
基于SpringBoot和PostGIS的省域“地理难抵点(最纵深处)”检索及可视化实践
目录 前言 1、研究背景 2、研究意义 一、研究目标 1、“地理难抵点”的概念 二、“难抵点”空间检索实现 1、数据获取与处理 2、计算流程 3、难抵点计算 4、WebGIS可视化 三、成果展示 1、华东地区 2、华南地区 3、华中地区 4、华北地区 5、西北地区 6、西南地…...
【Qt】详细介绍如何在Visual Studio Code中编译、运行Qt项目
Visual Studio Code一只用的顺手,写Qt的时候也能用VS Code开发就方便多了。 理论上也不算困难,毕竟Qt项目其实就是CMake(QMake的情况这里就暂不考虑了)项目,VS Code在编译、运行CMake项目还是比较成熟的。 这里笔者打…...
【Linux】修改 core 文件大小和路径
在 Linux 系统中,默认情况下,核心转储文件(core dump)会生成在当前工作目录下。为了将核心转储文件生成在指定路径下,可以通过以下方法进行配置。 1. 设置核心转储文件路径 Linux 系统提供了两种方式来指定核心转储文…...
本地部署大语言模型-DeepSeek
DeepSeek 是国内顶尖 AI 团队「深度求索」开发的多模态大模型,具备数学推理、代码生成等深度能力,堪称"AI界的六边形战士"。 Hostease AMD 9950X/96G/3.84T NVMe/1G/5IP/RTX4090 GPU服务器提供多种计费模式。 DeepSeek-R1-32B配置 配置项 规…...
【03】STM32F407 HAL 库框架设计学习
【03】STM32F407 HAL 库框架设计学习 摘要 本文旨在为初学者提供一个关于STM32F407微控制器HAL(Hardware Abstraction Layer)库框架设计的详细学习教程。通过本文,读者将从零开始,逐步掌握STM32F407的基本知识、HAL库的配置步骤…...
鸿蒙HarmonyOS NEXT开发:组件-样式-基础 2
// 1 // 2 ArkUI 基本语法 // 方舟开发框架(简称:ArkUI),是一套 构建HarmonyOS应用 界面 的框架。 // 构建页面的最小单位就是 "组件"。 // 组件名(参数) { // 内容 // } // .属性1() // .属性2() // .属性N() import text from @ohos.graphics.text // @En…...
SQLAlchemy系列教程:SQLAlchemy快速入门示例项目
SQLAlchemy是与数据库交互的Python开发人员不可或缺的库。这个强大的ORM允许使用python结构进行简单的数据库操作。设置过程很简单,并且允许可扩展的数据库应用程序开发。本文通过入门项目完整介绍SQLAlchemy的应用过程,包括安装依赖包,创建连…...
【Linux网络#10】:Https协议原理
📃个人主页:island1314 🔥个人专栏:Linux—登神长阶 ⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞 生活总是不会一帆风顺…...
蓝桥杯备考:记忆化搜索之function
这道题是有重复的问题的,所以我们可以选择记忆化搜索 #include <iostream> using namespace std; typedef long long LL; const int N 25; LL ret[N][N][N]; LL dfs(LL a,LL b, LL c) {if(a<0 || b<0 || c<0) return 1;if(a>20 || b>20 || c…...
在Nginx上配置并开启WebDAV服务的完整指南
在Nginx上配置并开启WebDAV服务的完整指南 如何在 Nginx 上开启 WebDAV 服务 要在 Nginx 上开启 WebDAV 服务,你需要配置 Nginx 以支持 WebDAV 请求。以下是详细的步骤: 1. 确保 Nginx 安装了 WebDAV 模块 Nginx 的 WebDAV 功能由 http_dav_module 模…...
mysql 全方位安装教程
下载 MySQL 【官网下载地址】 注意要选择较大的哪个安装包,小的安装包是一个安装器。 我们不用登录,直接下载 直接运行下载好的安装包 MySQL如果是 安装包安装, 可以图形化界面自主配置 如果是压缩包解压, 可以配置 配置文件, 可以解压安装到指定的…...
设计模式Python版 观察者模式
文章目录 前言一、观察者模式二、观察者模式示例 前言 GOF设计模式分三大类: 创建型模式:关注对象的创建过程,包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、原型模式和建造者模式。结构型模式:关注类和对象之间的组…...
如何在Python用Plot画出一个简单的机器人模型
如何在Python中使用 Plot 画出一个简单的模型 在下面的程序中,首先要知道机器人的DH参数,然后计算出每一个关节的位置,最后利用 plot 函数画出关节之间的连杆就可以了,最后利用 animation 库来实现一个动画效果。 import matplo…...
如何使用ArcGIS Pro制作横向图例:详细步骤与实践指南
ArcGIS Pro,作为Esri公司推出的新一代地理信息系统(GIS)平台,以其强大的功能和灵活的操作界面,在地理数据处理、地图制作和空间分析等领域发挥着重要作用。 在地图制作过程中,图例作为地图的重要组成部分&…...
MySQL 创建指定IP用户并赋予全部权限(兼容8.0以下及8.0以上版本)
在MySQL中,为用户指定访问IP并授予权限是常见的运维操作。但由于MySQL 8.0对用户创建和权限管理机制进行了升级,不同版本的操作存在差异。以下是针对MySQL 8.0以下版本和8.0及以上版本的具体实现方法。 一、MySQL 8.0以下版本(如5.7、5.6) 在MySQL 8.0之前,可以通过一条G…...
每日十个计算机专有名词 (7)
Metasploit 词源:Meta(超越,超出) exploit(漏洞利用) Metasploit 是一个安全测试框架,用来帮助安全专家(也叫渗透测试人员)发现和利用计算机系统中的漏洞。你可以把它想…...
【Python 数据结构 3.顺序表】
目录 一、顺序表基本概念 1.顺序表的概念 2.顺序表的元素插入 元素插入的步骤 3.顺序表的元素删除 元素删除的步骤 4.顺序表的元素查找 元素查找的步骤 5.顺序表的元素索引 元素索引的步骤 6.顺序表的元素修改 元素修改的步骤 二、Python中的顺序表 1.顺序表的定义 2.顺序表的插…...
C# 装箱(Boxing)与拆箱(Unboxing)
C# 装箱(Boxing)与拆箱(Unboxing) 在 C# 中,装箱和拆箱是与值类型(如结构体)和引用类型(如类)之间的转换相关的操作。它们是类型系统的一部分,但如果不正确使…...
[ComfyUI][AI生图]如何在Comfyui中安装插件管理器
如何在ComfyUI便携版中安装插件管理器 在现代软件环境中,图形用户界面(GUI)提供了一种直观的方式来与应用程序交互。ComfyUI是一个出色的GUI框架,它使用户能够通过图形化方式配置和管理他们的应用程序。特别是ComfyUI的便携版,它允许用户在没有安装的情况下使用这一工具,…...
从零开始构建高效Spring Boot应用:实战案例与最佳实践
摘要 本文旨在为初学者及有一定基础的开发者提供一份详尽的指南,以帮助大家深入理解并掌握如何使用Spring Boot框架来快速开发企业级应用程序。通过实际案例分析、代码示例以及架构设计思路分享,读者不仅能够学习到理论知识,还能获得宝贵的实…...
EdgeNext模型详解及代码复现
架构特点 EdgeNeXt是一种集CNN与Transformer于一体的混合架构,其核心创新在于引入了 分割深度转置注意力(SDTA)编码器 。这种设计巧妙地将深度卷积与自适应核大小以及转置注意力相结合,实现了最佳的精度-速度平衡。 SDTA编码器主要由两个组件构成: 特征编码模块 :受Res…...
SQL经典题型
查询不在表里的数据,一张学生表,一张学生的选课表,要求查出没有选课的学生? select students.student_name from students left join course_selection on students.student_idcourse_selection.student_id where course_selecti…...
