在golang for循环中使用goroutine引起的问题及分析
无意中搜到Handling 1 Million Requests per Minute with Go这篇博客,在评论区里有人发现文章中的代码有一个隐蔽的bug。在平时coding中这个问题比较容易被忽略,所以在此记录和分析下。 有问题的模拟代码如下。
package main
import (
"fmt"
"time"
)
type Payload struct {
data int
}
func (p *Payload) UploadToS3() error {
fmt.Println("payload data:",p.data)
return nil
}
func main() {
payloads := []Payload{Payload{1},Payload{2},Payload{3},Payload{4}}
for _,payload := range payloads {
go payload.UploadToS3()
}
time.Sleep(4 * time.Second)
}
运行上面的代码,输出都为payload data: 4
,而不是4个不同的值。这是由于for只在第一个Iteration声明payload,后续的迭代只是改变playload的值。因为for循环所在的goroutine在4个UploadToS3 goroutine之前运行,所以当UploadToS3开始被调用的时候playload的值已经确定,即slice的最后一个Payload。因为UploadToS3的receiver是*Payload
,所以golang编译器会在遇到payload.UploadToS3()
的时候自动使用&payload
去调用UploadToS3。因此4个goroutine取到的结果是同一个。如果UploadToS3()的receiver是Payload就不会有这个问题,因为编译器会复制一个临时的non-point receiver(Payload)传递给method(UploadToS3)。
当然由于goroutine运行顺序的不确定性,也有可能出现不是输出都为payload data: 4
的结果。
这个问题涉及的知识点比较多,忽略任何一点都可能忽略这个bug。