首先,纠正一下题主,在 Go 中是协程(goroutine),或者说用户态轻量级线程。下面来回答一下题主的两个问题:

问题 1:为什么放在 for 循环里面修改全局变量不生效?

这个应该是内存可见性(Memory Visibility)的问题,按照《Java并发编程实战》书中的所说:是指一个线程修改了对象状态后,其他线程能够看到发生的状态变化。那么在 Go 中如何来保证呢?推荐题主阅读《The Go Memory Model》或者中文版《Go 内存模型》,它详细介绍了在多个 goroutine 情况下,如何保证共享变量的可见性以及 Happens Before 原则。为了保证共享变量的可见性,该文档中的建议如下:

Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access

程序中多个 goroutine 同时访问某个数据,必须保证串行化访问。即需要增加同步逻辑,可以使用 sync 或者 sync/atomic 中的锁或原子类型来保证。另外,题主还可以尝试用 channel 来实现这个同步逻辑,示例代码请查看 codewalk-sharemem。通过这个代码来理解一下 Go 语言中的一个经典说法:Do not communicate by sharing memory; instead, share memory by communicating。

问题 2:为什么通过指针的方式可以修改成功?

这个问题本质上和第1个问题是没有区别的(昨晚特地找同事一起讨论了一下这个问题)。在数据竞争的情况下,未满足 Happens Before 原则,程序的行为是未定义的,不确定的,那为什么通过操作指针的方式可以成功呢?跟 @丁博 说的一样,我们要看下编译器的行为,分析一下汇编代码:

情况一:非指针方式,修改 running 的语句被优化掉了。

情况二:使用指针的方式,可以看到 running 被修改为 0

情况三:非指针方式,但是限制迭代次数,也可以看到 running 被修改为 0

所以综上所述,两个问题都是因为数据竞争导致编译器不确定的行为,只要增加同步机制保证即可。

另外,其他答主提到了指针问题,但是在 Go 的闭包里面引用外部变量其实就是指针。

参考