Go任务队列简介
\n任务队列(task queues)是高负载应用开发中的关键模式之一。它们可以组织异步数据处理、分配负载并提高系统的容错性。在Go语言中,得益于内置的并发机制——goroutine和通道——实现队列变得异常优雅且高效。
\n在本文中,我们将从零开始讲解如何在Go中创建任务队列。您将了解什么是goroutine和通道,如何组织工作池(worker pool)以及如何并行处理任务。本材料面向熟悉Go基本语法的初学者开发者。
\n\n基础:goroutine和通道
\n在进入队列之前,理解Go的两个基本概念很重要:
\n- \n
- Goroutine(协程)——轻量级执行线程。通过在函数调用前使用关键字
go启动。 \n - Channel(通道)——用于在goroutine之间传输数据的类型化“通道”。允许同步它们的工作。 \n
一个简单的goroutine和通道示例:
\npackage main\n\nimport "fmt"\n\nfunc main() {\n ch := make(chan string)\n\n go func() {\n ch <- "来自goroutine的问候!"\n }()\n\n msg := <-ch\n fmt.Println(msg)\n}\n\n在这个示例中,我们创建了一个通道ch,启动了一个匿名goroutine向通道发送消息,主goroutine接收了该消息。任务队列正是基于这一机制构建的。
实现一个简单的任务队列
\n最简单的任务队列是一个通道,我们将任务放入其中,并由一个或多个goroutine处理它们。来看一个基本示例:
\npackage main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc worker(id int, tasks <-chan int) {\n for task := range tasks {\n fmt.Printf("工作者 %d 开始任务 %d\\n", id, task)\n time.Sleep(time.Second) // 模拟工作\n fmt.Printf("工作者 %d 完成任务 %d\\n", id, task)\n }\n}\n\nfunc main() {\n tasks := make(chan int, 10) // 缓冲通道\n\n // 启动一个工作者\n go worker(1, tasks)\n\n // 发送5个任务\n for i := 1; i <= 5; i++ {\n tasks <- i\n }\n\n close(tasks) // 关闭通道\n time.Sleep(2 * time.Second) // 给处理留出时间\n}\n\n这里worker是一个从通道tasks读取数据的函数。通道是缓冲的(容量为10),因此我们可以发送多个任务而不阻塞。通道关闭后,for range循环会自动结束。
请注意:我们使用了<-chan int——这是一个只读通道。这是一个好习惯,明确表明工作者不应向此通道发送数据。
缓冲通道与非缓冲通道
\n在上面的示例中,我们使用了缓冲通道(make(chan int, 10))。两种通道类型的区别:
- \n
- 非缓冲通道——发送会阻塞,直到接收者读取数据。非常适合同步。 \n
- 缓冲通道——仅当缓冲区满时发送才会阻塞。允许累积任务并减少延迟。 \n
对于任务队列,通常使用缓冲通道,以便生产者可以快速发送任务并继续工作,而无需等待任务完成。
\n\nGo中的工作池(Worker Pool)
\n一个工作者好,多个工作者更好。工作池允许利用所有CPU核心并行处理任务。以下是实现方法:
\npackage main\n\nimport (\n "fmt"\n "sync"\n "time"\n)\n\nfunc worker(id int, tasks <-chan int, wg *sync.WaitGroup) {\n defer wg.Done()\n fo