4个场景教会你Go中Goroutine和通道是怎么用的
作者:不背锅运维
开篇
这段时间把主要精力都放在了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来等待定时器的触发事件,并执行相应的任务。这样可以在后台自动执行任务,而不需要阻塞主线程。
到此这篇关于4个场景教会你Go中Goroutine和通道是怎么用的的文章就介绍到这了,更多相关Go Goroutine通道内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!