Go并发陷阱之i的到底等于几?

今天写bug时碰到一个Go并发陷阱,是一道很经典的面试题,那就是:

func main() {
	for i := 0; i < 5; i++ {
		go func() {
			fmt.Println("current value: ", i)
		}()
	}
	time.Sleep(time.Second)
}

这段代码看起来会顺序输出0-4,但是其实打印出来的结果却是:
在这里插入图片描述
当然结果也不一定就是图片的结果,为什么会这样?
因为此时协程中读取的imain中循环的i是相同的地址空间,这就有可能导致goroutine在使用i执行的时候,i的地址被main改变,因为二者共享同一个i,所以就导致图片上的结果,main早早的把循环跑完(i == 5)而进行 1秒休眠,而goroutine才刚开始要执行到 fmt.println("current value is :" , i ) 这行代码。因为main和goroutine共享同一个地址!所以打印出来的i也就是5的内存地址了。
那么如何正确打印出想要的内容呢?
因为Go是值传递,那么在传递参数时,传递的参数其实是实际值的拷贝,二者虽然值相同,但是内存地址不同,这样也就不会出现goroutine中的值的内存地址随着main的内存地址而改变的问题了,因为goroutine中的值只是main中传参那一时刻的值的拷贝值。

for i := 0; i < 5; i++ {
		go func(v int) {
			fmt.Println("协程中 i 的值是: ", v)
		}(i)
	}
	time.Sleep(time.Second)

运行结果如下:
在这里插入图片描述
实际开发中也是同理,可能核心参数不会像i这么简单,但是如果只是main 和 goroutine 共享一个地址,也会出现一些莫名其妙的问题。

那么在给goroutine传参的时候,如果我们想要对main中的变量进行操作而不是复制一个新的值(比如对数组中添加元素,Go的值传递会导致协程中的变量被改变了而实际参数的内容不改变),可以选择传递变量指针,这样进行操作时,两个协程操作的就是同一个地址上的变量。 那么 如果是 切片/map 类型的元素,需要传递元素地址吗?
答案是不需要:从map的源码中可知:

// makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *maptype, hint int, h *hmap) *hmap {
	if hint < 0 || hint > int(maxSliceCap(t.bucket.size)) {
		hint = 0
	}
	...

make() 的时候返回的其实是 类型指针,所以把map作为参数传递,实际上传递的就是 map的类型指针,所以直接对map可以在goroutine中对map进行操作。

slice也是同理,切片的结构体源码如下:

//runtime/slice.go
type slice struct {
    array unsafe.Pointer
    len   int
    cap   int
}

实际上切片传递的是切片的第一个元素的指针副本,这个指针指向的是底层数组的第一个元素,所以虽然看起来没有传递切片的类型指针,但其实已经传递的是切片的类型指针了。在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值