面试题答案
一键面试问题原因分析
- 共享变量未保护:在闭包内,goroutine可能共享某些变量,而这些变量在并发读写时没有适当的同步机制,从而导致数据竞争。例如,多个goroutine可能同时修改同一个全局变量或者在闭包中引用的外层作用域变量,没有加锁保护。
- 闭包对变量的引用特性:闭包会引用外部作用域的变量,当多个goroutine并发执行闭包时,可能因为对同一个外部变量的引用,在不同时间点修改该变量,造成数据不一致。
解决方案
- 使用互斥锁(Mutex):
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
var result int
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(num int) {
defer wg.Done()
mu.Lock()
result += num
mu.Unlock()
}(i)
}
wg.Wait()
fmt.Println("Final result:", result)
}
这里使用sync.Mutex
来保护result
变量,确保同一时间只有一个goroutine可以修改它。
2. 使用读写锁(RWMutex):如果读操作远多于写操作,可以使用读写锁。读操作时多个goroutine可以同时进行,写操作时会独占资源。
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.RWMutex
var result int
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
if i%2 == 0 {
wg.Add(1)
go func() {
defer wg.Done()
mu.RLock()
_ = result
mu.RUnlock()
}()
} else {
wg.Add(1)
go func(num int) {
defer wg.Done()
mu.Lock()
result += num
mu.Unlock()
}(i)
}
}
wg.Wait()
fmt.Println("Final result:", result)
}
- 使用通道(Channel):通过通道在goroutine之间传递数据,避免共享变量。
package main
import (
"fmt"
"sync"
)
func main() {
resultChan := make(chan int)
var wg sync.WaitGroup
for i := 0; i < 10; i++ {
wg.Add(1)
go func(num int) {
defer wg.Done()
resultChan <- num
}(i)
}
go func() {
wg.Wait()
close(resultChan)
}()
var result int
for val := range resultChan {
result += val
}
fmt.Println("Final result:", result)
}
闭包和匿名函数在并发场景中的行为特点
- 变量引用:闭包会捕获并引用外部作用域的变量,在并发场景中,这意味着多个goroutine可能引用并操作同一个外部变量,容易引发数据竞争问题。
- 延迟绑定:闭包内对外部变量的引用是延迟绑定的,即闭包执行时才会获取变量的值。这在并发场景中可能导致意外结果,例如在循环中创建闭包,多个闭包可能引用到循环结束后变量的最终值,而不是每次循环时变量的当前值。
- 独立执行上下文:每个由匿名函数启动的goroutine都有独立的执行上下文,但是闭包共享外部变量,所以要特别注意变量的并发访问问题。