go 循环变量捕获 陷阱

发布时间 2023-08-01 11:54:19作者: 若-飞

以下这样的循环代码,最后会发现spaceId都是同一个!!!!

	for _, spaceId := range spaceIds {
		task := func() {
			uc.log.WithContext(ctx).Errorf("SpaceUsecase::GetSpaces uc.badgeClt.Get spaceId:%v start!!!!", spaceId)
		}
		getBadgesTasks = append(getBadgesTasks, task)
	}

这是因为在任务函数 task 中,spaceId 是一个外部变量,它在任务函数被创建时捕获了当前循环迭代的值。这种情况被称为“循环变量捕获”,是 Go 语言中的一个常见陷阱。

在循环中启动 goroutine 时,如果不采取特定的措施,那么所有 goroutine 都会共享同一个循环变量,最终会导致它们所有的 spaceId 都是相同的。在这个例子中,由于 task 函数被添加到 getBadgesTasks 切片中,因此每次调用 task 时,都会使用相同的 spaceId 值。

为了避免这个问题,可以将循环变量的值传递给任务函数作为参数,从而使每个 goroutine 都有自己的 spaceId 值。例如:

for _, spaceId := range spaceIds {
    task := func(spaceId int32) {
        // 在函数内使用 spaceId 参数
        // ...
    }
    getBadgesTasks = append(getBadgesTasks, func() { task(spaceId) })
}

在这种情况下,每个任务函数都会创建一个新的闭包,其中 spaceId 的值被捕获并传递给任务函数的参数,在每次迭代时都会创建一个新的闭包和参数。这样,每个 goroutine 都会有自己的 spaceId 值,可以避免相互影响的问题。