
在Go语言中,于`for`循环内使用`go`关键字启动的子例程会并发执行。然而,若不妥善管理,主程序可能在这些并发任务完成前终止。本文将深入探讨循环中Go协程的并发机制,并重点介绍如何利用`sync.WaitGroup`确保所有并发任务安全完成,同时警示常见的闭包陷阱。
当我们在Go语言的for循环内部使用go关键字启动一个函数(即创建一个goroutine)时,这个函数会作为一个独立的并发执行单元运行,与主程序流并行。例如,考虑以下代码片段:
package main
import (
"fmt"
"time"
)
func processValue(value string) {
fmt.Printf("Processing: %s\n", value)
time.Sleep(time.Millisecond * 50) // 模拟耗时操作
fmt.Printf("Finished: %s\n", value)
}
func main() {
dataMap := map[string]string{
"key1": "Apple",
"key2": "Banana",
"key3": "Cherry",
}
fmt.Println("Launching goroutines...")
for key := range dataMap {
// 在这里,Map[key] 的值会在每个goroutine启动时被评估并作为参数传递
go processValue(dataMap[key])
}
// 主goroutine可能在子goroutine完成前退出
time.Sleep(time.Second * 1) // 给予一些时间让子goroutine运行
fmt.Println("Main program exiting.")
}在这个例子中,processValue(dataMap[key])会为dataMap中的每个键值对启动一个独立的goroutine。这意味着processValue("Apple")、processValue("Banana")和processValue("Cherry")将几乎同时开始执行。
关键点: 当go subroutine(Map[key])被调用时,Map[key]表达式会在当前循环迭代中被求值,其结果作为参数传递给新启动的goroutine。因此,每个goroutine都会接收到其启动时key所对应的特定值,而不会受到后续循环迭代中key值变化的影响。
立即学习“go语言免费学习笔记(深入)”;
上述示例存在一个常见问题:主main goroutine可能在所有子goroutine完成其工作之前就执行完毕并退出。一旦主goroutine退出,整个程序就会终止,无论是否有子goroutine仍在运行。为了确保所有并发任务都能在程序退出前完成,Go标准库提供了sync.WaitGroup。
sync.WaitGroup是一个计数器,用于等待一组goroutine完成。它有三个主要方法:
下面是使用sync.WaitGroup来正确管理循环中并发goroutine的示例:
package main
import (
"fmt"
"sync"
"time"
)
// 模拟一个耗时操作的子例程
func worker(id int, value string) {
fmt.Printf("Goroutine %d processing value: %s\n", id, value)
time.Sleep(time.Millisecond * 100) // 模拟工作
fmt.Printf("Goroutine %d finished processing value: %s\n", id, value)
}
func main() {
dataMap := map[string]string{
"key1": "Value A",
"key2": "Value B",
"key3": "Value C",
}
var wg sync.WaitGroup // 声明一个 WaitGroup
counter := 0 // 用于为goroutine提供一个唯一的ID
fmt.Println("Launching goroutines...")
for _, val := range dataMap { // 遍历map,我们这里只需要值
wg.Add(1) // 每启动一个goroutine,计数器加1
counter++
// 关键:将当前迭代的 `val` 作为参数传递给匿名函数。
// 这样可以避免闭包捕获循环变量的最终值问题。
go func(currentID int, currentValue string) {
defer wg.Done() // 确保无论goroutine如何退出,计数器都会减1
worker(currentID, currentValue)
}(counter, val) // 立即执行匿名函数,并传入当前迭代的变量副本
}
fmt.Println("All goroutines launched. Waiting for them to complete...")
wg.Wait() // 阻塞主goroutine,直到所有Add都被Done抵消
fmt.Println("All goroutines completed. Main program exiting.")
}在这个改进的例子中:
Shell本身是一个用C语言编写的程序,它是用户使用Linux的桥梁。Shell既是一种命令语言,又是一种程序设计语言。作为命令语言,它交互式地解释和执行用户输入的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。它虽然不是Linux系统核心的一部分,但它调用了系统核心的大部分功能来执行程序、建立文件并以并行的方式协调各个程序的运行。因此,对于用户来说,shell是最重要的实用程序,深入了解和熟练掌握shell的特性极其使用方法,是用好Linux系统
24
在Go语言中,当你在一个闭包(匿名函数)中引用循环变量时,可能会遇到一个常见的“陷阱”。如果goroutine的启动和执行之间存在时间差,而闭包直接引用了循环变量,那么所有goroutine可能会最终看到循环变量的最后一个值,而不是它们启动时期望的值。
考虑以下错误的示例(与我们上面的正确示例形成对比):
// 这是一个错误的示例,展示了闭包陷阱
for i := 0; i < 3; i++ {
go func() {
// 这里的 i 是循环变量本身,而不是它的副本。
// 当这个goroutine真正执行时,i 可能已经是循环的最终值(例如 3)。
fmt.Printf("Value of i: %d\n", i)
}()
}
// output might be:
// Value of i: 3
// Value of i: 3
// Value of i: 3为了避免这个陷阱,你有两种主要方法:
for i := 0; i < 3; i++ {
go func(val int) { // val 是 i 的一个独立副本
fmt.Printf("Value of i: %d\n", val)
}(i) // 立即将 i 的当前值作为参数传入
}for i := 0; i < 3; i++ {
currentI := i // 创建 i 的局部副本
go func() {
fmt.Printf("Value of i: %d\n", currentI) // 引用局部副本
}()
}这两种方法都能确保每个goroutine操作的是其启动时循环变量的正确值。
在某些特定场景下,例如构建一个服务器、守护进程或一个持续运行的服务,主goroutine本身就设计为长时间运行,并不会轻易退出(例如,它可能在一个无限循环中监听网络请求)。在这种情况下,sync.WaitGroup可能不是强制性的,因为程序不会在子goroutine完成前意外终止。
// 示例:一个简单的服务器循环
func main() {
fmt.Println("Server starting...")
// 假设这里有一个处理请求的循环
for {
// 模拟接收到一个请求
requestID := time.Now().UnixNano()
fmt.Printf("Received request %d\n", requestID)
go func(id int64) {
// 处理请求,可能需要较长时间
time.Sleep(time.Second * 2)
fmt.Printf("Finished processing request %d\n", id)
}(requestID)
time.Sleep(time.Second * 1) // 模拟请求间隔
}
// 这个主循环永远不会退出,除非外部信号中断程序
}即便如此,使用sync.WaitGroup仍然是管理并发任务的良好实践,它能帮助你清晰地追踪和控制并发任务的数量和状态,尤其是在需要等待所有处理完成才能执行某些清理或关闭操作时。
在Go语言中,利用for循环和go关键字实现并发任务是高效且常见的模式。理解其核心行为,并正确管理goroutine的生命周期至关重要:
遵循这些原则,将帮助您编写出高效、稳定且易于维护的Go并发程序。
以上就是Go语言中循环并发协程的行为与管理的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号