当前位置: 首页 > 面试题库 >

这是因为go编译器优化了代码吗?

葛永丰
2023-03-14
问题内容
package main

import "time"

func main() {
    i := 1
    go func() {
        for {
            i++
        }
    }()
    <-time.After(1 * time.Second)
    println(i)
}

输出始终为1

但是,绝对可以使for循环多次遍历1s 。

我认为,i在闭包是imainFUNC。

请参见下面的代码。

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会增加,这使得更新与数据竞争可见于maingoroutine。

==================
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....)。