Go语言中的并发与多线程:高级进阶

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

Go中的并发与多线程:进阶水平



Go(Golang)从设计之初就是为编写可扩展和高性能应用程序而生的语言。其核心是强大的并发模型,基于goroutine和channel。如果你已经掌握了go func()和简单channel的基础示例,那么是时候进入下一个层次了。在本文中,我们将深入探讨Go中高级并发模式,帮助你编写可靠、高效且安全的代码。



1. Goroutine生命周期管理:Context和WaitGroup



最常见的问题之一是goroutine泄漏。一个无限等待channel数据的goroutine,如果channel未正确关闭,将永远不会终止。Go中有两个主要工具用于管理goroutine的生命周期:sync.WaitGroup和context.Context。



sync.WaitGroup用于等待完成

当你需要等待一组固定数量的goroutine完成时,请使用sync.WaitGroup。这是一种简单而有效的机制。



package main

import (\t"fmt"\t"sync"\t"time")

func worker(id int, wg *sync.WaitGroup) {\tdefer wg.Done() // 退出时减少计数器\tfmt.Printf("Worker %d 开始工作", id)\ttime.Sleep(time.Second)\tfmt.Printf("Worker %d 结束工作", id)}

func main() {\tvar wg sync.WaitGroup

\tfor i := 1; i <= 3; i++ {\t\twg.Add(1) // 启动前增加计数器\t\tgo worker(i, &wg)\t}

\twg.Wait() // 阻塞直到计数器变为0\tfmt.Println("所有worker已完成")}


context.Context用于取消和截止时间

当goroutine在循环中运行或执行长时间操作时,它需要能够根据外部信号正确终止。context.Context是传递取消信号、截止时间或超时信号的标准方式。



package main

import (\t"context"\t"fmt"\t"time")

func longRunningTask(ctx context.Context) {\tfor {\t\tselect {\t\tcase <-ctx.Done():\t\t\tfmt.Println("Goroutine收到停止信号")\t\t\treturn\t\tdefault:\t\t\ttime.Sleep(500 * time.Millisecond)\t\t\tfmt.Println("工作中...")\t\t}\t}}

func main() {\tctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\tdefer cancel() // 务必调用cancel以释放资源

\tgo longRunningTask(ctx)

\ttime.Sleep(3 * time.Second)\tfmt.Println("Main已完成")}


2. Channel:缓冲、关闭与Fan-Out/Fan-In模式



Channel是goroutine之间同步和传递数据的主要方式。在进阶层面,理解缓冲channel和非缓冲channel之间的区别、正确关闭它们以及使用聚合模式非常重要。



缓冲 vs 非缓冲

非缓冲channel(无缓冲)会阻塞发送方,直到接收方读取数据。缓冲channel允许发送多条消息而无需等待,直到缓冲区填满。选择取决于任务:对于严格同步,使用非缓冲channel;对于负载均衡,使用缓冲channel。



Fan-Out/Fan-In模式

这是最强大的模式之一。Fan-Out——将任务分发给多个goroutine(worker)。Fan-In——从多个goroutine收集结果到一个channel。



package main

import (\t"fmt"\t"sync")

func producer(nums ...int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor _, n := range nums {\t\t\tout <- n\t\t}\t\tclose(out)\t}()\treturn out}

func square(in <-chan int) <-chan int {\tout := make(chan int)\tgo func() {\t\tfor n := range in {\t\t\tout <- n * n\t\t}\t\tclose(out)\t}()\treturn out}

func merge(channels ...<-chan int) <-chan int {

博客

书籍推荐