当前位置: 首页 > news >正文

深入浅出 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 语句监听了两个通道 ch1ch2。由于 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 语言&#xff1a;协程(Goroutine)详解 引言 Go 语言的协程&#xff08;goroutine&#xff09;是其并发模型的核心特性之一。协程允许你轻松地编写并发代码&#xff0c;而不需要复杂的线程管理和锁机制。通过协程&#xff0c;你可以同时执行多个任务&#xff0c;并…...

vLLM代码推理Qwen2-VL多模态

由于近期代码微调以及测试都是在远程服务器上&#xff0c;因此LLamafactory-cli webui 以及vLLM的ui均无法使用&#xff0c;因此不断寻求解决方案&#xff0c;我提供一个解决方案&#xff0c;LLamafactory微调完成的模型需要合并为一个完整模型后再使用vLLM进行代码推理测试微调…...

DNS云解析有什么独特之处?

在数字化浪潮中&#xff0c;每一次网页点击、视频加载或在线交易背后&#xff0c;都依赖着域名系统&#xff08;DNS&#xff09;的高效运转。传统DNS架构的局限性&#xff08;如单点故障、延迟高、安全脆弱&#xff09;在云计算时代被彻底颠覆&#xff0c;DNS云解析作为新一代解…...

视频流畅播放相关因素

视频播放的流畅度是一个综合性问题&#xff0c;涉及从视频文件本身到硬件性能、网络环境、软件优化等多个环节。以下是影响流畅度的关键因素及优化建议&#xff1a; 一、视频文件本身 1. 分辨率与帧率 1.问题&#xff1a;高分辨率&#xff08;如4K&#xff09;或高帧率&#…...

Python实现一个类似MybatisPlus的简易SQL注解

文章目录 前言实现思路定义一个类然后开始手撸这个微型框架根据字符串获取到所定义的DTO类构建返回结果装饰器解析字符串&#xff0c;获得变量SQL字符串拼接 使用装饰器 前言 在实际开发中&#xff0c;根据业务拼接SQL所需要考虑的内容太多了。于是&#xff0c;有没有一种办法…...

linux一些使用技巧

linux一些使用技巧 文件名称和路径的提取切换用户执行当前脚本一行演示单引号与双引号的使用curl命令仅输出响应头信息,不输出body体文件名称和路径的提取 文件路径为 /tmp/tkgup/test.sh 方式获取文件名获取文件路径获取文件全路径方式一basename ${file}dirname ${file}real…...

小模型和小数据可以实现AGI吗

小模型和小数据很难实现真正的 通用人工智能&#xff08;AGI, Artificial General Intelligence&#xff09;&#xff0c;但在特定任务或受限环境下&#xff0c;可以通过高效的算法和优化方法实现“近似 AGI” 的能力。 1. 为什么小模型小数据难以实现 AGI&#xff1f; AGI 需…...

io学习----->文件io

思维导图&#xff1a; 一.文件io的概念 文件IO&#xff1a;指程序和文件系统之间的数据交互 特点&#xff1a; 1.不存在缓冲区&#xff0c;访问速度慢 2.不可以移植&#xff0c;依赖于操作系统 3.可以访问不同的文件类型(软连接&#xff0c;块设备等) 4.文件IO属于系统调…...

kubernetes介绍

文章目录 kubernetes概述kubernetes组件kubernetes概念 kubernetes概述 kubernetes&#xff0c;是一个全新的基于容器技术的分布式架构领先方案&#xff0c;是Google开源的的容器编排工具。 ​ kubernetes的本质是一组服务器集群&#xff0c;它可以在集群的每个节点上运行特定…...

如何高效准备PostgreSQL认证考试?

高效准备 PostgreSQL 中级认证考试&#xff0c;可从知识储备、技能提升、模拟考试等方面入手&#xff0c;以下是具体建议&#xff1a; 深入学习理论知识 系统学习核心知识&#xff1a;依据考试大纲&#xff0c;对 PostgreSQL 的体系结构、数据类型、SQL 语言、事务处理、存储过…...

如何使用Briefing打造私有视频会议系统结合内网穿透异地远程连接

文章目录 前言1.关于briefing2.本地部署briefing3.使用briefing4.cpolar内网穿透工具安装5.创建远程连接公网地址6.固定briefing公网地址 前言 在这个‘云’字当道的时代&#xff0c;远程办公、异地恋已经成了生活常态。视频聊天自然也就成了日常操作。但一不小心&#xff0c;…...

XHR请求解密:抓取动态生成数据的方法

在如今动态页面大行其道的时代&#xff0c;传统的静态页面爬虫已无法满足数据采集需求。尤其是在目标网站通过XHR&#xff08;XMLHttpRequest&#xff09;动态加载数据的情况下&#xff0c;如何精准解密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 开发的广阔领域中&#xff0c;RemoteViews 是一个独特且重要的概念&#xff0c;它为开发者提供了一种在其他进程中显示视图结构的有效方式。从本质上讲&#xff0c;RemoteViews 并非传统意义上在当前应用进程内直接渲染和操作的 View&#xf…...

编译可以在Android手机上运行的ffmpeg程序

下载代码 git clone gitgithub.com:FFmpeg/FFmpeg.git git checkout n7.0建立build目录 mkdir build cd build创建build.sh脚本 vim build.sh这段脚本的主要功能是配置和编译 FFmpeg&#xff0c;使其能够在 Android 平台上运行&#xff0c;通过设置不同的架构和 API 级别&am…...

Verilog学习方法—基础入门篇(一)

前言&#xff1a; 在FPGA开发中&#xff0c;Verilog HDL&#xff08;硬件描述语言&#xff09;是工程师必须掌握的一项基础技能。它不仅用于描述数字电路&#xff0c;还广泛应用于FPGA的逻辑设计与验证。对于初学者来说&#xff0c;掌握Verilog的核心概念和基本语法&#xff0…...

本地jar包添加到 maven

进入到 你的 maven bin文件夹下 执行cmd &#xff0c;然后执行命令 mvn install:install-file -Dfilepath/to/your/artifact.jar -DgroupIdyour.group.id -DartifactIdyour-artifact-id -Dversion1.0 -Dpackagingjar 替换path/to/your/artifact.jar为你的JAR文件路径&#xf…...

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.…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

Ubuntu系统下交叉编译openssl

一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机&#xff1a;Ubuntu 20.04.6 LTSHost&#xff1a;ARM32位交叉编译器&#xff1a;arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...

Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)

文章目录 1.什么是Redis&#xff1f;2.为什么要使用redis作为mysql的缓存&#xff1f;3.什么是缓存雪崩、缓存穿透、缓存击穿&#xff1f;3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)

Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败&#xff0c;具体原因是客户端发送了密码认证请求&#xff0c;但Redis服务器未设置密码 1.为Redis设置密码&#xff08;匹配客户端配置&#xff09; 步骤&#xff1a; 1&#xff09;.修…...

听写流程自动化实践,轻量级教育辅助

随着智能教育工具的发展&#xff0c;越来越多的传统学习方式正在被数字化、自动化所优化。听写作为语文、英语等学科中重要的基础训练形式&#xff0c;也迎来了更高效的解决方案。 这是一款轻量但功能强大的听写辅助工具。它是基于本地词库与可选在线语音引擎构建&#xff0c;…...

重启Eureka集群中的节点,对已经注册的服务有什么影响

先看答案&#xff0c;如果正确地操作&#xff0c;重启Eureka集群中的节点&#xff0c;对已经注册的服务影响非常小&#xff0c;甚至可以做到无感知。 但如果操作不当&#xff0c;可能会引发短暂的服务发现问题。 下面我们从Eureka的核心工作原理来详细分析这个问题。 Eureka的…...

推荐 github 项目:GeminiImageApp(图片生成方向,可以做一定的素材)

推荐 github 项目:GeminiImageApp(图片生成方向&#xff0c;可以做一定的素材) 这个项目能干嘛? 使用 gemini 2.0 的 api 和 google 其他的 api 来做衍生处理 简化和优化了文生图和图生图的行为(我的最主要) 并且有一些目标检测和切割(我用不到) 视频和 imagefx 因为没 a…...

并发编程 - go版

1.并发编程基础概念 进程和线程 A. 进程是程序在操作系统中的一次执行过程&#xff0c;系统进行资源分配和调度的一个独立单位。B. 线程是进程的一个执行实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。C.一个进程可以创建和撤销多个线程;同一个进程中…...

Razor编程中@Html的方法使用大全

文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...

破解路内监管盲区:免布线低位视频桩重塑停车管理新标准

城市路内停车管理常因行道树遮挡、高位设备盲区等问题&#xff0c;导致车牌识别率低、逃费率高&#xff0c;传统模式在复杂路段束手无策。免布线低位视频桩凭借超低视角部署与智能算法&#xff0c;正成为破局关键。该设备安装于车位侧方0.5-0.7米高度&#xff0c;直接规避树枝遮…...