4个场景教会你Go中Goroutine和通道是怎么用的
作者:不背锅运维 发布时间:2024-04-23 09:45:05
开篇
这段时间把主要精力都放在了K8S上,差点把Golang给忘了。那本篇就分享一下并发相关的内容(Goroutine和通道)。 本篇给出4个场景,这4个场景是在运维开发工作中较为常见的且也是比较典型的场景。通过这些代码示例,让你知道Goroutine和通道在运维开发中是怎么应用的。总而言之,言而总之,当涉及到处理并发和并行任务时,Goroutine和通道是非常强悍的,可以让我们开发出高效的、牛逼的并发程序。
实战场景
1.并发执行任务的场景
场景:假设需要编写一个程序,用于批量执行某个操作(例如部署应用程序、更新配置等)到多台服务器上。
供参考的代码示例:
package main
import (
"fmt"
"sync"
)
// 服务器结构体
type Server struct {
Name string
// 其他服务器相关的字段
}
// 执行任务的函数
func executeTask(server Server, task string) {
// 连接服务器并执行任务的逻辑
fmt.Printf("执行任务 [%s] 到服务器 [%s]\n", task, server.Name)
// 执行操作的代码
}
func main() {
// 服务器列表
servers := []Server{
{Name: "Server1"},
{Name: "Server2"},
{Name: "Server3"},
// 添加更多的服务器
}
// 任务列表
tasks := []string{"部署应用程序", "更新配置", "执行命令", "其他任务"}
// 创建一个任务通道,用于发送任务到Goroutine池
taskChannel := make(chan string)
// 创建一个等待组,用于等待所有Goroutine执行完毕
var wg sync.WaitGroup
wg.Add(len(servers))
// 启动Goroutine池
for _, server := range servers {
go func(server Server) {
// 标记任务完成时,通知等待组减少一个计数
defer wg.Done()
// 从任务通道中接收任务,并执行
for task := range taskChannel {
executeTask(server, task)
}
}(server)
}
// 将任务发送到任务通道
for _, task := range tasks {
taskChannel <- task
}
// 关闭任务通道,表示所有任务都已发送
close(taskChannel)
// 等待所有Goroutine执行完毕
wg.Wait()
}
上面的代码,创建了一个Goroutine池,每个Goroutine代表一台服务器,通过通道将任务分发给Goroutine进行并发执行。每个Goroutine负责连接到服务器,并执行相应的操作。这样可以加速任务的执行,同时提高资源利用率。
2.并发日志处理的场景
场景:假设需要将大量的日志数据并发地写入到不同的目标中(例如文件、数据库、消息队列等)。
供参考的代码示例:
package main
import (
"fmt"
"log"
"os"
"sync"
)
// 日志结构体
type Log struct {
Message string
// 其他日志相关的字段
}
func main() {
// 创建一个日志通道,用于发送日志数据
logChannel := make(chan Log)
// 创建一个等待组,用于等待所有Goroutine执行完毕
var wg sync.WaitGroup
// 启动一个Goroutine处理日志写入操作
wg.Add(1)
go func() {
// 标记日志写入完毕时,通知等待组减少一个计数
defer wg.Done()
// 打开文件进行日志写入
file, err := os.OpenFile("log.txt", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 创建一个日志写入器
logger := log.New(file, "", log.LstdFlags)
// 从日志通道中接收日志数据,并写入到目标中
for log := range logChannel {
logger.Println(log.Message)
}
}()
// 并发地向日志通道发送日志数据
for i := 0; i < 10; i++ {
wg.Add(1)
go func(index int) {
// 标记发送完日志数据时,通知等待组减少一个计数
defer wg.Done()
// 构造日志数据
log := Log{
Message: fmt.Sprintf("日志消息 %d", index),
// 设置其他日志字段
}
// 发送日志数据到日志通道
logChannel <- log
}(i)
}
// 关闭日志通道,表示所有日志数据都已发送
close(logChannel)
// 等待日志写入操作的Goroutine执行完毕
wg.Wait()
}
在上面的代码中,使用了一个专门的Goroutine来处理日志写入操作,该Goroutine从一个日志通道中读取日志数据,并将其写入到目标中。其他的Goroutine可以并发地向该通道发送日志数据,而不会因为写入操作而阻塞。
3.异步任务调度的场景
在实际的运维工作中,有种场景是需要按照一定的调度策略异步执行一些任务。比如这样的场景:定期备份数据库、定时清理无效数据等。
供参考的代码示例:
package main
import (
"fmt"
"time"
)
// 执行任务的函数
func executeTask(task string) {
// 执行任务的逻辑
fmt.Println("执行任务:", task)
// 具体的任务操作代码
}
func main() {
// 创建一个定时器,每隔一段时间触发一次
timer := time.NewTimer(5 * time.Second)
// 启动一个Goroutine等待定时器的触发事件
go func() {
// 等待定时器的触发事件
<-timer.C
// 定时器触发后执行任务
executeTask("定时任务1")
// 重新设置定时器,以实现循环调度
timer.Reset(10 * time.Second)
}()
// 主线程继续执行其他任务
// ...
// 阻塞主线程,保持程序运行
select {}
}
4.并发任务协作的场景
在某些情况下,你可能需要多个Goroutine之间进行协作和同步。例如,一个Goroutine负责生产任务,而另一个Goroutine负责消费任务并进行处理。你可以使用通道来实现生产者-消费者模型。生产者将任务发送到通道中,而消费者从通道中接收任务并进行处理。通过这种方式,可以实现任务的有效分配和协同处理。
package main
import (
"fmt"
"time"
)
// 任务结构体
type Task struct {
ID int
Data string
// 其他任务相关的字段
}
// 生产者,负责生产任务并发送到通道
func producer(ch chan<- Task) {
for i := 1; i <= 10; i++ {
task := Task{
ID: i,
Data: fmt.Sprintf("任务 %d", i),
// 设置其他任务字段
}
ch <- task
fmt.Println("生产任务:", task.Data)
time.Sleep(500 * time.Millisecond) // 模拟生产任务的耗时
}
close(ch) // 关闭通道,表示所有任务都已生产完毕
}
// 消费者,负责从通道接收任务并进行处理
func consumer(ch <-chan Task) {
for task := range ch {
fmt.Println("消费任务:", task.Data)
// 执行任务的处理逻辑
time.Sleep(1 * time.Second) // 模拟处理任务的耗时
}
}
func main() {
// 创建一个任务通道,用于生产者和消费者之间的通信
taskChannel := make(chan Task)
// 启动生产者Goroutine
go producer(taskChannel)
// 启动多个消费者Goroutine
for i := 1; i <= 3; i++ {
go consumer(taskChannel)
}
// 阻塞主线程,保持程序运行
select {}
}
在上面的代码种,使用了定时器(time.Timer)结合Goroutine来实现异步任务调度。通过启动一个Goroutine来等待定时器的触发事件,并执行相应的任务。这样可以在后台自动执行任务,而不需要阻塞主线程。
来源:https://mp.weixin.qq.com/s/bqZ6gAsOZoVEMSCeOuLbiQ


猜你喜欢
- 一、QtDesigner介绍Qt Designer 是一款GUI界面工具,可以实现将UI设计界面转为Python代码的工具;二、安装 QTd
- 一、下载MySQL首先,去数据库的官网http://www.mysql.com下载MySQL。点击进入后的首页如下: 然后点击do
- 一、Mysql分区类型1、RANGE 分区:基于属于一个给定连续区间的列值,把多行分配给分区。2、HASH分区:基于用户定义的表达式的返回值
- 我们用下了asp代码简单统计了下载一个文件需要的时间:<%Function DownloadTime(intFileSize
- 一、效果展示:1、表单的图片上传项:- 新增时默认一个空白Input框- 更新时展示以往上传存放的图片,- 点击【查看】浏览完整大小- 点击
- 我就废话不多说了,大家还是直接看代码吧~package mainimport ("fmt""reflect&q
- 1. 概述Python中 asyncio 模块内置了对异步IO的支持,用于处理异步IO;是Python 3.4版本引入的标准库。asynci
- 这是一个给新手学习代码的帖子,包含以下内容:如何使用UBB代码,如何用js与剪贴板交互,如何使用textRange对象,如何使用自定义的快捷
- 功能是从客户端向服务发送一个字符串, 服务器收到后将字符串重新发送给客户端,同时,在连接建立之后,服务器可以向客户端发送任意多的字符串客户端
- 本文实例为大家分享了Vue使用localStorage存储数据的具体代码,供大家参考,具体内容如下通过下面这个案例来了解localStora
- django orm 有个defer方法,指定模型排除的字段。如下返回的Queryset, 排除‘username', 'i
- 一、复合查询1.1 多表查询实际开发中往往数据来自不同的表,所以需要多表查询,但是可以将多张表做笛卡尔积后的表当做是一张表,也就是单表查询。
- 本文中的示例主要是解决在函数间不能传递多个(32个以上)参数的问题,解题的具体思路就是采用记录类型作为函数的输入和返回值,所以我们需要先定义
- 微信小程序中使用地图(map)组件,通过点击(tap)获取经纬度,按照官方的回应,暂时是没法做到的,从地图组件API多有残缺判断,怀疑是个实
- “你不必严格遵守这些原则,违背它们也不会被处以宗教刑罚。但你应当把这些原则看成警铃,若违背了其中的一条,那么警铃就会响起
- 前言上个篇章中我们主要介绍了OpenTelemetry的客户端的一些数据生成方式,但是客户端的数据最终还是要发送到服务端来进行统一的采集整合
- 1.格式化输出 chop 是rtrim()的别名;ltrim() trim()nl2br()将\n转换成<br>print,ec
- 这篇文章主要介绍了Vue子组件内的props对象里的default参数是如何定义Array、Object、或Function默认值的正确写法
- 本文实例为大家分享了python实现滑雪游戏的具体代码,供大家参考,具体内容如下# coding: utf-8# 滑雪小游戏import s
- 前言在几周前,我开始工作于一个证券投资组合网站。虽然我只能使用 React 完成整个网站,但我决定使用 Go 来创建一个可以处理某些任务(例