package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Second)
println(i)
}
输出始终为1
。
但是,绝对可以使for
循环多次遍历1s 。
我认为,i
在闭包是i
在main
FUNC。
请参见下面的代码。
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
println("+1")
}
}()
<-time.After(1 * time.Second)
println(i)
}
在多行“ +1”之后,输出正好是预期的很大数目。
记忆模型
2014年5月31日版本
介绍
Go内存模型指定了一种条件,在这种条件下,可以保证在一个goroutine中读取变量可以观察到在不同goroutine中写入同一变量所产生的值。
忠告
修改由多个goroutine同时访问的数据的程序必须序列化此类访问。
要序列化访问,请使用通道操作或其他同步原语(例如sync和sync / atomic包中的原语)保护数据。
如果您必须阅读本文档的其余部分以了解程序的行为,那么您就太聪明了。
别聪明
同步化
var a string func hello() { go func() { a = "hello" }() print(a) }
分配给a不会跟随任何同步事件,因此不能保证任何其他goroutine都会遵守它。实际上,积极的编译器可能会删除整个go语句。
i
通过增量i++
(i = i + 1
)分配给时,没有任何同步事件,因此不能保证任何其他goroutine都会遵守该事件。实际上,积极的编译器可能会删除整个i++
语句。
例如,
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
}
}()
<-time.After(1 * time.Millisecond)
println(i)
}
输出:
1
goroutine简化为:
"".main.func1 STEXT nosplit size=2 args=0x8 locals=0x0
0x0000 00000 (elide.go:7) TEXT "".main.func1(SB), NOSPLIT, $0-8
0x0000 00000 (elide.go:7) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB)
0x0000 00000 (elide.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x0000 00000 (elide.go:9) JMP 0
对于编译器,
for {
i++
}
可以通过永久增加一个寄存器(基本上是一个无操作for
循环)来实现。
for { }
插入print
语句后,
package main
import "time"
func main() {
i := 1
go func() {
for {
i++
println("+1")
}
}()
<-time.After(1 * time.Millisecond)
println(i)
}
输出:
+1
+1
<< SNIP >>
+1
+1
432
goroutine扩展为
"".main.func1 STEXT size=81 args=0x8 locals=0x18
0x0000 00000 (elide.go:7) TEXT "".main.func1(SB), $24-8
0x0000 00000 (elide.go:7) MOVQ (TLS), CX
0x0009 00009 (elide.go:7) CMPQ SP, 16(CX)
0x000d 00013 (elide.go:7) JLS 74
0x000f 00015 (elide.go:7) SUBQ $24, SP
0x0013 00019 (elide.go:7) MOVQ BP, 16(SP)
0x0018 00024 (elide.go:7) LEAQ 16(SP), BP
0x001d 00029 (elide.go:7) FUNCDATA $0, gclocals·a36216b97439c93dafebe03e7f0808b5(SB)
0x001d 00029 (elide.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
0x001d 00029 (elide.go:8) MOVQ "".&i+32(SP), AX
0x0022 00034 (elide.go:9) INCQ (AX)
0x0025 00037 (elide.go:10) PCDATA $0, $0
0x0025 00037 (elide.go:10) CALL runtime.printlock(SB)
0x002a 00042 (elide.go:10) LEAQ go.string."+1\n"(SB), AX
0x0031 00049 (elide.go:10) MOVQ AX, (SP)
0x0035 00053 (elide.go:10) MOVQ $3, 8(SP)
0x003e 00062 (elide.go:10) PCDATA $0, $0
0x003e 00062 (elide.go:10) CALL runtime.printstring(SB)
0x0043 00067 (elide.go:10) PCDATA $0, $0
0x0043 00067 (elide.go:10) CALL runtime.printunlock(SB)
0x0048 00072 (elide.go:9) JMP 29
0x004a 00074 (elide.go:9) NOP
0x004a 00074 (elide.go:7) PCDATA $0, $-1
0x004a 00074 (elide.go:7) CALL runtime.morestack_noctxt(SB)
0x004f 00079 (elide.go:7) JMP 0
goroutine的增加的复杂性意味着编译器不再考虑将寄存器专用于的值i
。内存中的值i
会增加,这使得更新与数据竞争可见于main
goroutine。
==================
WARNING: DATA RACE
Read at 0x00c420094000 by
main goroutine:
main.main()
/home/peter/gopath/src/lucky.go:14 +0xac
Previous write at 0x00c420094000 by
goroutine 5:
main.main.func1()
/home/peter/gopath/src/lucky.go:9 +0x4e
Goroutine 5 (running) created at:
main.main()
/home/peter/gopath/src/lucky.go:7 +0x7a
==================
为了获得预期的结果,请添加一些同步,
package main
import (
"sync"
"time"
)
func main() {
mx := new(sync.Mutex)
i := 1
go func() {
for {
mx.Lock()
i++
mx.Unlock()
}
}()
<-time.After(1 * time.Second)
mx.Lock()
println(i)
mx.Unlock()
}
输出:
41807838
我经常遇到这种情况。乍一看,我认为,“这是糟糕的编码;我正在执行一个方法两次,必然会得到相同的结果。”但想到这里,我不得不怀疑编译器是否像我一样聪明,并能得出相同的结论。 编译器的行为是否取决于 方法的内容?假设它看起来像这样(有点类似于我现在的真实代码): 除非对这些对象来自的任何存储进行处理不当的异步更改,否则如果连续运行两次,肯定会返回相同的内容。但是,如果它看起来像这样(为了论证而无意义的
问题内容: 我目前正在翻译中编写一个针对Java字节码的玩具编译器。 我想知道是否可以在编写.class文件之前在发出的字节码中进行各种简单的窥孔优化的目录,也许是摘要。我实际上知道一些具有此功能的库,但是我想自己实现。 问题答案: 您知道Proguard吗?http://proguard.sourceforge.net/ 这是一个很棒的字节码优化器,它实现了很多优化。请参阅常见问题解答以获取列表
问题内容: 假设我在C代码中有类似的内容。我知道您可以使用a 代替,以使编译器不对其进行编译,但是出于好奇,我问编译器是否也可以解决此问题。 我认为这对于Java编译器来说更为重要,因为它不支持。 问题答案: 在Java中,if内的代码甚至都不是已编译代码的一部分。它必须编译,但不会写入已编译的字节码。它实际上取决于编译器,但我不知道没有对它进行优化的编译器。规则在JLS中定义: 优化的编译器可能
我编译了以下C代码: 使用命令 .下面是输出中的 Bar 函数: 我有几个关于这个汇编代码的问题: > 如果函数体中既没有使用也没有使用rsp,那么"",""和""的目的是什么? 为什么和自动包含C函数的参数(分别为和)而不从堆栈中读取它们? 我尝试将Foo的大小增加到88字节(11s),指令变成了。将我的结构设计为“圆形”大小以避免乘法指令(以优化数组访问)是否有意义?指令被替换为:
在最近的一个相关问题中,我发现以下代码 存在编译器错误,x不是init。(初始化)。令我惊讶的是,编译器无法推理init。尤其是因为这段代码似乎不需要任何运行时推理,如下所示: 在指示的第2行,被设置为。 第2行和第3行之间没有代码。 所以到达第3行,必然是所以init是不可避免的。 我不知道这是否正确。我隐约记得,编译器优化可以改变语句的执行顺序。这在这里起作用了吗?有没有可能在3号线和4号线之
如果关闭了编译器优化(gcc-o0...),那么说'volatile'关键字没有区别是可以的吗? 我制作了一些示例“C”程序,并且仅当打开编译器优化时,才在生成的汇编代码中看到易失性和非易失性之间的区别,即((gcc-o1....)。