面试题答案
一键面试匿名函数作用域分析
- 匿名函数
func() { data = append(data, i) }
的作用域:- 该匿名函数在
for
循环内部定义。它可以访问到for
循环外部的data
变量(因为data
定义在循环外部),同时也可以访问到for
循环中的i
变量。 - 但是这里存在一个陷阱,由于Go语言中
for
循环的i
变量是复用的,所有匿名函数捕获的实际上是同一个i
变量。
- 该匿名函数在
数据竞争问题
- 数据竞争描述:
- 这里存在数据竞争问题。多个goroutine同时对
data
进行append
操作,append
操作不是线程安全的,会导致数据竞争。 - 另外,由于所有匿名函数捕获的是同一个
i
变量,当for
循环结束后,i
的值变为5
,所以所有goroutine可能会向data
中添加5
,而不是预期的0
到4
。
- 这里存在数据竞争问题。多个goroutine同时对
避免数据竞争的方法
- 使用互斥锁(Mutex):
package main import ( "fmt" "sync" ) func main() { var data []int var mu sync.Mutex var wg sync.WaitGroup for i := 0; i < 5; i++ { wg.Add(1) go func(j int) { defer wg.Done() mu.Lock() data = append(data, j) mu.Unlock() }(i) } wg.Wait() fmt.Println(data) }
- 在这个改进版本中,使用
sync.Mutex
来保护对data
的操作。mu.Lock()
用于锁定互斥锁,防止其他goroutine同时修改data
,mu.Unlock()
用于解锁。同时,通过将i
作为参数传递给匿名函数,每个goroutine捕获的是不同的i
值。
- 在这个改进版本中,使用
- 使用通道(Channel):
package main import ( "fmt" ) func main() { dataCh := make(chan int, 5) var data []int for i := 0; i < 5; i++ { go func(j int) { dataCh <- j }(i) } for i := 0; i < 5; i++ { data = append(data, <-dataCh) } close(dataCh) fmt.Println(data) }
- 在这个版本中,使用通道
dataCh
来传递数据。每个goroutine将i
的值发送到通道中,然后在主goroutine中从通道中接收数据并添加到data
切片中,避免了对data
的并发访问导致的数据竞争问题。
- 在这个版本中,使用通道