golang迭代变量

在学习 golang 的过程中,遇到一个比较有意思的问题,也是书中指出的需要重点注意的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
arr := []int{2,3,4,5}
var ap []func()int
// 循环把求值的函数放入到函数数组
for _, v := range arr {
ap = append(ap, func() int {
return v * v
})
}

// 此处遍历并依次调用函数
for _, v := range ap {
fmt.Print(v())
}

预期结果我想应该是 4,9,16,25 吧?结果实际输出出乎意料,竟然输出了 25 25 25 25

原因是 v 是在 range 的块作用域里面,在循环里创建的所有函数变量共享相同的变量 - 一个可访问的存储位置,而不是固定的值

可以简单的输出一下 v 的地址来直观的看待此问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
func main() {
f := test()
fmt.Println(f())
fmt.Println(f())

arr := []int{2,3,4,5}
var ap []func()int
for _, v := range arr {
fmt.Println(&v) // 输出 v 的地址
ap = append(ap, func() int {
return v * v
})
}

for _, v := range ap {
fmt.Print(v())
}
}
// 输出
0xc00005c0a8
0xc00005c0a8
0xc00005c0a8
0xc00005c0a8

可以看到,每次迭代过程, v 的地址都是一样的,所以,匿名函数里面保存的 v 也是一样的,循环结束后,v 最终的值变成了 25 ,所以,在遍历 ap 的时候,输出的结果全都是 25(因为引用的都是同一个变量)

并发和并行的个人理解

并发和并行这两个概念基本上在学习多线程编程的时候总会碰到,虽然只有一字之差,但其实是两个不一样的概念,从英文单词的角度来看就能发现他的不一样之处,并发的英文单词是 Concurrency 而并行的英文单词是 Parallelism

我在理解这两种概念之前,看了很多篇文章,确实很多文章写得很优秀,比如知乎上的一些非常形象的比喻,足以让你一看就明白。但是,当我看完这些概念之后出去撒泡尿回来准备总结一下的时候,我脑海中只有什么“和尚挑水”、“美女”等故事关键词,要我重新组织语言我确实不知道该说些什么

直到我看了一篇写得非常好的文章,以下内容,是我自己结合文章的小小总结,如果想查看原文,请文章底部链接:

在开始之前,先深刻理解这句话

并发指的是程序的结构,并行指的是运行时的状态

并行(Parallelism)

顾名思义,并行是指程序同时执行,判断程序是否并行,只要看同一时刻存在两个执行流即可,真的不需要过度去解读这个词语。并行 => 同时执行

并发(Concurrency)

并发指的是程序的“结构”,当我们说这个程序是并发的,实际上,这句话应当表述成“这个程序采用了支持并发的设计”。好,既然并发指的是人为设计的结构,那么怎样的程序结构才叫做支持并发的设计?

正确的并发设计的标准是:使多个操作可以在重叠的时间段内进行

举个例子:程序需要执行两个任务,获取一段数据,把这段数据先写入文件(注:这个数据非常大,可能需要耗时 10s),然后再写入数据库(5s)

先来看一段不支持并发的设计:

1
2
3
write_to_file(data)
write_to_db(data)
// 这段代码一共耗时 10s + 5s = 15s

接下来再看支持并发的设计(使用go协程):

1
2
3
go write_to_file(data)
go write_to_db(data)
// 这段代码一共耗时 10s

为什么第二段代码耗时 10s?因为他们是并发执行的,也就是说在 write_to_file写入的 10s 这个时间段内,write_to_db操作也在进行,所以符合上面所说的使多个操作可以在重叠的时间段内进行

所以总结如下:并发并不要求必须并行,比如单核cpu上的多任务系统,并发的要求是任务能切分成独立执行的片段。而并行关注的是同时执行,必须是多(核)cpu,要能并行的程序必须是支持并发的。

参考文章:

还在疑惑并发和并行?