Skip to content

Commit 51e3318

Browse files
committed
go: per-loop vs per-iteration
1 parent 0ac326d commit 51e3318

File tree

2 files changed

+32
-11
lines changed

2 files changed

+32
-11
lines changed

go-note/tricks.rst

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -740,8 +740,11 @@ Go 如何复制map
740740
testShareMap()
741741
}
742742
743-
闭包问题
743+
循环变量与闭包问题 ⭐️
744744
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
745+
go 的循环变量是 per-loop 的而不是 per-iteration 绑定的,这个特性导致了很多隐蔽反直觉并且难以排查的bug。
746+
go 官方目前仍在讨论是否要改善这个问题:https://github.com/golang/go/discussions/56010 。
747+
举一些常见例子防止踩坑:
745748

746749
.. code-block:: go
747750
@@ -753,7 +756,6 @@ Go 如何复制map
753756
)
754757
755758
// 闭包问题
756-
757759
func testClosure() {
758760
data := []string{"one", "two", "three"}
759761
for _, v := range data {
@@ -762,14 +764,14 @@ Go 如何复制map
762764
}()
763765
}
764766
time.Sleep(1 * time.Second) // not good, just for demo
765-
// three three three
767+
// three three three // 打印的都是最后一个值
766768
}
767769
768-
// 两种方式解决:1.使用一个for 循环临时变量
770+
// 两种方式解决:1.使用一个块临时变量
769771
func testClosure1() {
770772
data := []string{"one", "two", "three"}
771773
for _, v := range data {
772-
vcopy := v
774+
vcopy := v // 使用一个临时变量,经常可以看到 v := v 这种写法
773775
go func() {
774776
fmt.Println(vcopy)
775777
}()
@@ -778,7 +780,7 @@ Go 如何复制map
778780
// one two three (may wrong order)
779781
}
780782
781-
// 方法2:使用传给匿名goroutine参数,推荐使用这种方式
783+
// 方法2:使用传给匿名goroutine参数,个人推荐使用这种方式
782784
func testClosure2() {
783785
data := []string{"one", "two", "three"}
784786
for _, v := range data {
@@ -790,6 +792,7 @@ Go 如何复制map
790792
// one two three (may wrong order)
791793
}
792794
795+
// 指针接收者调用场景
793796
type field struct {
794797
name string
795798
}
@@ -808,12 +811,29 @@ Go 如何复制map
808811
time.Sleep(1 * time.Second)
809812
}
810813
811-
func main() {
812-
// testClosure()
813-
// testClosure1()
814-
// testClosure2()
815-
testField()
814+
// 结构体示例
815+
values := []MyStruct{MyStruct{1}, MyStruct{2}, MyStruct{3}}
816+
output := []*MyStruct{}
817+
for _, v := range values {
818+
// v := v
819+
output = append(output, &v) // 不要直接引用一个循环变量的地址
816820
}
821+
fmt.Println("output: ", output) // 打印相同的地址
822+
823+
总结一下:
824+
825+
- 不要直接对循环变量取地址
826+
- 不要在 goroutine 中直接使用循环变量
827+
- 如果循环变量(非指针)调用函数是一个指针接收者,调用值需要拷贝一个临时变量再调用
828+
829+
解决方式:
830+
831+
1. 创建一个临时局部变量(`v:=v`)
832+
2. 作为参数传入(解决goroutine场景)
833+
834+
参考:
835+
836+
- https://nullprogram.com/blog/2014/06/06/ Per Loop vs. Per Iteration Bindings
817837

818838
Failed Type Assertions
819839
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ sphinx_rtd_theme==0.5.0
33
sphinx-autobuild==2020.9.1
44
docutils < 0.17
55
readthedocs-sphinx-search==0.1.0rc3
6+
Jinja2==3.0.1

0 commit comments

Comments
 (0)