深入浅出 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)是其并发模型的核心特性之一。协程允许你轻松地编写并发代码,而不需要复杂的线程管理和锁机制。通过协程,你可以同时执行多个任务,并…...
vLLM代码推理Qwen2-VL多模态
由于近期代码微调以及测试都是在远程服务器上,因此LLamafactory-cli webui 以及vLLM的ui均无法使用,因此不断寻求解决方案,我提供一个解决方案,LLamafactory微调完成的模型需要合并为一个完整模型后再使用vLLM进行代码推理测试微调…...
DNS云解析有什么独特之处?
在数字化浪潮中,每一次网页点击、视频加载或在线交易背后,都依赖着域名系统(DNS)的高效运转。传统DNS架构的局限性(如单点故障、延迟高、安全脆弱)在云计算时代被彻底颠覆,DNS云解析作为新一代解…...
视频流畅播放相关因素
视频播放的流畅度是一个综合性问题,涉及从视频文件本身到硬件性能、网络环境、软件优化等多个环节。以下是影响流畅度的关键因素及优化建议: 一、视频文件本身 1. 分辨率与帧率 1.问题:高分辨率(如4K)或高帧率&#…...
Python实现一个类似MybatisPlus的简易SQL注解
文章目录 前言实现思路定义一个类然后开始手撸这个微型框架根据字符串获取到所定义的DTO类构建返回结果装饰器解析字符串,获得变量SQL字符串拼接 使用装饰器 前言 在实际开发中,根据业务拼接SQL所需要考虑的内容太多了。于是,有没有一种办法…...
linux一些使用技巧
linux一些使用技巧 文件名称和路径的提取切换用户执行当前脚本一行演示单引号与双引号的使用curl命令仅输出响应头信息,不输出body体文件名称和路径的提取 文件路径为 /tmp/tkgup/test.sh 方式获取文件名获取文件路径获取文件全路径方式一basename ${file}dirname ${file}real…...
小模型和小数据可以实现AGI吗
小模型和小数据很难实现真正的 通用人工智能(AGI, Artificial General Intelligence),但在特定任务或受限环境下,可以通过高效的算法和优化方法实现“近似 AGI” 的能力。 1. 为什么小模型小数据难以实现 AGI? AGI 需…...
io学习----->文件io
思维导图: 一.文件io的概念 文件IO:指程序和文件系统之间的数据交互 特点: 1.不存在缓冲区,访问速度慢 2.不可以移植,依赖于操作系统 3.可以访问不同的文件类型(软连接,块设备等) 4.文件IO属于系统调…...
kubernetes介绍
文章目录 kubernetes概述kubernetes组件kubernetes概念 kubernetes概述 kubernetes,是一个全新的基于容器技术的分布式架构领先方案,是Google开源的的容器编排工具。 kubernetes的本质是一组服务器集群,它可以在集群的每个节点上运行特定…...
如何高效准备PostgreSQL认证考试?
高效准备 PostgreSQL 中级认证考试,可从知识储备、技能提升、模拟考试等方面入手,以下是具体建议: 深入学习理论知识 系统学习核心知识:依据考试大纲,对 PostgreSQL 的体系结构、数据类型、SQL 语言、事务处理、存储过…...
如何使用Briefing打造私有视频会议系统结合内网穿透异地远程连接
文章目录 前言1.关于briefing2.本地部署briefing3.使用briefing4.cpolar内网穿透工具安装5.创建远程连接公网地址6.固定briefing公网地址 前言 在这个‘云’字当道的时代,远程办公、异地恋已经成了生活常态。视频聊天自然也就成了日常操作。但一不小心,…...
XHR请求解密:抓取动态生成数据的方法
在如今动态页面大行其道的时代,传统的静态页面爬虫已无法满足数据采集需求。尤其是在目标网站通过XHR(XMLHttpRequest)动态加载数据的情况下,如何精准解密XHR请求、捕获动态生成的数据成为关键技术难题。本文将深入剖析XHR请求解密…...
坐标变换介绍与机器人九点标定的原理
【备注】本文的C#代码在下面链接中可以下载:Opencv的C#九点标定代码资源-CSDN文库 https://download.csdn.net/download/qq_34047402/90452336 一、坐标变换的介绍 1.绕原点旋转的坐标变换 一个点(x,y)绕原点旋转u度,其旋转后的坐标(x1,y1)如何计算? 2.绕任意点的坐标变…...
串口调试助手Alien v5.198新版发布
v5.198 更改点: 1.增加USB打印机支持 2.支持特殊波特率/自定义波特率 3.支持窗口透明调整 4.支持接收框文本左/中/右对齐,粗体字,自动换行 5.支持接收时间戳 6.HEX接收自动换行 7.支持文本颜色主题 8.支持文本字体修改 9.增加菜单/增状态栏显示当前接口 下载 alien_v5.198.7z …...
解锁Android RemoteViews:跨进程UI更新的奥秘
一、RemoteViews 简介 在 Android 开发的广阔领域中,RemoteViews 是一个独特且重要的概念,它为开发者提供了一种在其他进程中显示视图结构的有效方式。从本质上讲,RemoteViews 并非传统意义上在当前应用进程内直接渲染和操作的 View…...
编译可以在Android手机上运行的ffmpeg程序
下载代码 git clone gitgithub.com:FFmpeg/FFmpeg.git git checkout n7.0建立build目录 mkdir build cd build创建build.sh脚本 vim build.sh这段脚本的主要功能是配置和编译 FFmpeg,使其能够在 Android 平台上运行,通过设置不同的架构和 API 级别&am…...
Verilog学习方法—基础入门篇(一)
前言: 在FPGA开发中,Verilog HDL(硬件描述语言)是工程师必须掌握的一项基础技能。它不仅用于描述数字电路,还广泛应用于FPGA的逻辑设计与验证。对于初学者来说,掌握Verilog的核心概念和基本语法࿰…...
本地jar包添加到 maven
进入到 你的 maven bin文件夹下 执行cmd ,然后执行命令 mvn install:install-file -Dfilepath/to/your/artifact.jar -DgroupIdyour.group.id -DartifactIdyour-artifact-id -Dversion1.0 -Dpackagingjar 替换path/to/your/artifact.jar为你的JAR文件路径…...
C# Unity 唐老狮 No.6 模拟面试题
本文章不作任何商业用途 仅作学习与交流 安利唐老狮与其他老师合作的网站,内有大量免费资源和优质付费资源,我入门就是看唐老师的课程 打好坚实的基础非常非常重要: 全部 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 如果你发现了文章内特殊的字体格式,…...
项目工坊 | Python驱动淘宝信息爬虫
目录 前言 1 完整代码 2 代码解读 2.1 导入模块 2.2 定义 TaoBao 类 2.3 search_infor_price_from_web 方法 2.3.1 获取下载路径 2.3.2 设置浏览器选项 2.3.3 反爬虫处理 2.3.4 启动浏览器 2.3.5 修改浏览器属性 2.3.6 设置下载行为 2.3.7 打开淘宝登录页面 2.3.…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
在Ubuntu中设置开机自动运行(sudo)指令的指南
在Ubuntu系统中,有时需要在系统启动时自动执行某些命令,特别是需要 sudo权限的指令。为了实现这一功能,可以使用多种方法,包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法,并提供…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
ios苹果系统,js 滑动屏幕、锚定无效
现象:window.addEventListener监听touch无效,划不动屏幕,但是代码逻辑都有执行到。 scrollIntoView也无效。 原因:这是因为 iOS 的触摸事件处理机制和 touch-action: none 的设置有关。ios有太多得交互动作,从而会影响…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...
【C++进阶篇】智能指针
C内存管理终极指南:智能指针从入门到源码剖析 一. 智能指针1.1 auto_ptr1.2 unique_ptr1.3 shared_ptr1.4 make_shared 二. 原理三. shared_ptr循环引用问题三. 线程安全问题四. 内存泄漏4.1 什么是内存泄漏4.2 危害4.3 避免内存泄漏 五. 最后 一. 智能指针 智能指…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...
