Golang中闭包与Goroutine的结合使用
Golang中闭包与Goroutine的结合使用
我是Go语言的新手,带着许多其他语言的思维习惯。使用下面的代码,程序运行正确,count 的值是5。但如果我使用 go Even(arr),count 就变成了0。我原以为 Even(arr) 会形成一个闭包,捕获 count 变量,这样在goroutine(闭包)中处理时仍然可以访问并修改 count 的值到5。
我哪里理解错了?
var count = 0
func Even(arr []int) {
for _, item := range arr {
if item%2 == 0 {
count++
}
}
}
func main() {
arr := makeRange(1, 100000)
Even(arr)
fmt.Println(count)
}
更多关于Golang中闭包与Goroutine的结合使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html
感谢您花时间查看这个问题。
我特意尝试破坏这段代码,以展示共享变量可能引发的问题。
我之前不了解 WaitGroup 这个概念。感谢您介绍它。起初我很困惑为什么全局变量 count 总是零。现在我明白了。这是因为主函数在 goroutine 有机会更新全局变量之前就结束了。因此它保持为 0。
通过引入 WaitGroup,我能够向自己证明,共享全局变量的功能确实会出问题。正如我所预料的,count 是一个不稳定的值 🙂
func main() {
arr := makeRange(1, 10000)
firstHalf := arr[0 : len(arr)/2]
secondHalf := arr[len(arr)/2:]
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
Even(firstHalf)
wg.Done()
}()
go func() {
Even(secondHalf)
wg.Done()
}()
wg.Wait()
fmt.Println(count)
}
更多关于Golang中闭包与Goroutine的结合使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
首先,你的 Even 函数并没有对 count 形成闭包。count 是一个全局变量,main 和 Even 函数都可以访问它。
其次,虽然我理解你了解其他语言,因此我猜你只是想学习如何在 Go 中实现并发,但以下是我会如何实现你的程序:
我认为正确的方法是修改 Even 函数,使其不再接收切片并更新全局 count 变量,而是应该改成这样:
func Even(arr []int) int {
count := 0
for _, item := range arr {
if item%2 == 0 {
count++
}
}
return count
}
然后你在 main 函数中这样使用它:
func main() {
arr := makeRange(1, 100000)
count := Even(arr)
fmt.Println(count)
}
如果不做这个改动,假设你有另一个 goroutine 也在运行,并且使用你的 Even 函数来统计偶数值的数量,那么它们将共享同一个 count 变量,并且都会返回错误的值。以这种新方式编写函数,可以安全地并发使用,并且为我们设置了一个稍微不同的例子,我想用它来演示使用 goroutine 的动机:
go 关键字启动一个 goroutine 但不会等待它完成。当你想运行一个函数,然后在不等待(或者在等待)该函数完成的情况下做其他事情时,你会想要使用 goroutine。在你的例子中,因为你只是统计偶数值的数量,然后只有在得到计数后才想打印出来,所以在另一个 goroutine 中运行 Even 不会带来任何好处(无论是性能上还是学习上)。不过,通过我上面所做的改动,你可以在多个 goroutine 中运行多个 Even 函数:
import "sync"
func main() {
arr := makeRange(1, 100000)
wg := sync.WaitGroup{}
wg.Add(2)
firstHalf, secondHalf := 0, 0
go func() {
firstHalf = Even(arr[:50000])
wg.Done()
}()
go func() {
secondHalf = Even(arr[50000:])
wg.Done()
}()
wg.Wait()
fmt.Println(firstHalf + secondHalf)
}
这个实现在两个 goroutine 中执行 Even 函数,并使用一个 sync.WaitGroup 来等待 goroutine 完成对切片两半部分中偶数的计数。
你的理解基本正确,闭包确实会捕获外部变量。但问题在于并发执行时,主goroutine和Even goroutine之间存在竞态条件。
当你使用go Even(arr)时:
- 主goroutine继续执行,立即打印
count Evengoroutine在后台执行计数- 两者同时访问
count变量,没有同步机制
关键点:
- 闭包确实捕获了
count变量 - 但goroutine是并发执行的,主goroutine可能在
Evengoroutine完成前就打印了结果
示例代码展示问题:
package main
import (
"fmt"
"time"
)
var count = 0
func Even(arr []int) {
for _, item := range arr {
if item%2 == 0 {
count++
}
}
}
func makeRange(min, max int) []int {
arr := make([]int, max-min+1)
for i := range arr {
arr[i] = min + i
}
return arr
}
func main() {
arr := makeRange(1, 10)
// 使用goroutine
go Even(arr)
// 主goroutine立即执行
fmt.Println("Count before goroutine completes:", count) // 可能输出0
// 等待goroutine完成
time.Sleep(100 * time.Millisecond)
fmt.Println("Count after waiting:", count) // 应该输出5
}
要正确同步,可以使用sync.WaitGroup:
package main
import (
"fmt"
"sync"
)
var count = 0
var mu sync.Mutex
func Even(arr []int, wg *sync.WaitGroup) {
defer wg.Done()
for _, item := range arr {
if item%2 == 0 {
mu.Lock()
count++
mu.Unlock()
}
}
}
func main() {
arr := makeRange(1, 10)
var wg sync.WaitGroup
wg.Add(1)
go Even(arr, &wg)
wg.Wait() // 等待goroutine完成
fmt.Println(count) // 输出5
}
闭包版本:
func main() {
arr := makeRange(1, 10)
var wg sync.WaitGroup
var mu sync.Mutex
count := 0
wg.Add(1)
go func() {
defer wg.Done()
for _, item := range arr {
if item%2 == 0 {
mu.Lock()
count++
mu.Unlock()
}
}
}()
wg.Wait()
fmt.Println(count) // 输出5
}
核心问题不是闭包机制,而是并发执行时缺乏同步。闭包正确捕获了变量,但你需要确保在读取结果前goroutine已经完成计算。

