gpt4 book ai didi

go - 为什么 goroutine 饿死了?

转载 作者:行者123 更新时间:2023-12-01 22:22:47 24 4
gpt4 key购买 nike

我的客户很简单。还有两个 goroutine:

  • 主 goroutine:只是“for”循环,但带有调用函数。
  • 第二个 goroutine:这个 goroutine 只是 panic 。

  • package main

    import (
    "runtime"
    )

    func main() {
    runtime.GOMAXPROCS(1)
    go func() {
    panic(1)
    }()
    for {
    Test()
    }
    }

    func Test() {
    _ = make([]byte, 200)
    a := 1
    for {
    a++
    if a == 10 {
    break
    }
    }
    }

    我的 golang 版本是 go1.14.3。

    在 main goroutine 中有不被内联且不小的函数调用,所以 main goroutine 应该被 sysmon 抢占。第二个 goroutine 将有机会执行,并 panic 。

    但是什么也没发生。请帮忙!

    GODEBUG=asyncpreemptoff=1 go run main.go

    最佳答案

    好吧,我必须先说这个问题确实不适合 SO,因为它暗示了某种讨论——这已经在问题的原始版本的评论中发生了——最好在 the mailing list 上提出反而。

    但是好的,让我们尝试解决您的问题的 v2,其中包含以下代码:

    package main

    import (
    "runtime"
    )

    func main() {
    runtime.GOMAXPROCS(1)
    go func() {
    panic(1)
    }()
    for {
    Test()
    }
    }

    func Test() {
    _ = make([]byte, 200)
    a := 1
    for {
    a++
    if a == 10 {
    break
    }
    }
    }

    如果我们要求编译器转储它为您的程序生成的汇编代码——通过运行
    go build -gcflags=-S preempt.go 2>preempt.S

    (转储到 stderr ,这就是为什么 2>... ),在 linux/amd64 和 Go 1.14.3 上,我们将得到(省略):
    # command-line-arguments
    "".main STEXT size=80 args=0x0 locals=0x18
    0x0000 00000 (preempt.go:7) TEXT "".main(SB), ABIInternal, $24-0
    0x0000 00000 (preempt.go:7) MOVQ (TLS), CX
    0x0009 00009 (preempt.go:7) CMPQ SP, 16(CX)
    0x000d 00013 (preempt.go:7) JLS 73
    0x000f 00015 (preempt.go:7) SUBQ $24, SP
    0x0013 00019 (preempt.go:7) MOVQ BP, 16(SP)
    0x0018 00024 (preempt.go:7) LEAQ 16(SP), BP
    0x001d 00029 (preempt.go:7) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (preempt.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x001d 00029 (preempt.go:7) FUNCDATA $2, gclocals·9fb7f0986f647f17cb53dda1484e0f7a(SB)
    0x001d 00029 (preempt.go:8) PCDATA $0, $0
    0x001d 00029 (preempt.go:8) PCDATA $1, $0
    0x001d 00029 (preempt.go:8) MOVQ $1, (SP)
    0x0025 00037 (preempt.go:8) CALL runtime.GOMAXPROCS(SB)
    0x002a 00042 (preempt.go:9) MOVL $0, (SP)
    0x0031 00049 (preempt.go:9) PCDATA $0, $1
    0x0031 00049 (preempt.go:9) LEAQ "".main.func1·f(SB), AX
    0x0038 00056 (preempt.go:9) PCDATA $0, $0
    0x0038 00056 (preempt.go:9) MOVQ AX, 8(SP)
    0x003d 00061 (preempt.go:9) CALL runtime.newproc(SB)
    0x0042 00066 (preempt.go:13) CALL "".Test(SB)
    0x0047 00071 (preempt.go:13) JMP 66
    0x0049 00073 (preempt.go:13) NOP
    0x0049 00073 (preempt.go:7) PCDATA $1, $-1
    0x0049 00073 (preempt.go:7) PCDATA $0, $-1
    0x0049 00073 (preempt.go:7) CALL runtime.morestack_noctxt(SB)
    0x004e 00078 (preempt.go:7) JMP 0
    <…>
    "".Test STEXT nosplit size=17 args=0x0 locals=0x0
    0x0000 00000 (preempt.go:17) TEXT "".Test(SB), NOSPLIT|ABIInternal, $0-0
    0x0000 00000 (preempt.go:17) FUNCDATA $0, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (preempt.go:17) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (preempt.go:17) FUNCDATA $2, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB)
    0x0000 00000 (preempt.go:17) PCDATA $0, $0
    0x0000 00000 (preempt.go:17) PCDATA $1, $0
    0x0000 00000 (preempt.go:17) MOVL $1, AX
    0x0005 00005 (preempt.go:20) JMP 10
    0x0007 00007 (preempt.go:21) INCQ AX
    0x000a 00010 (preempt.go:22) CMPQ AX, $9
    0x000e 00014 (preempt.go:22) JNE 7
    0x0010 00016 (<unknown line number>) PCDATA $0, $-2
    0x0010 00016 (<unknown line number>) PCDATA $1, $-2
    0x0010 00016 (<unknown line number>) RET
    <…>

    此处列表以 main 的主体开始,然后以 Test 的主体继续。

    main 中,最感兴趣的是以下位:
        0x0042 00066 (preempt.go:13)    CALL    "".Test(SB)
    0x0047 00071 (preempt.go:13) JMP 66

    这基本上是您的 for 循环变成了 CALLTest 函数,然后是无条件 JMPCALL 的地址。

    如您所见,目前还没有任何代码可以帮助抢占。

    现在让我们看看 Test 的代码。

    首先要注意的是,看不到 slice 分配的痕迹:它被优化掉了。

    但现在让我们看一下函数的有点神秘的声明,内容如下:
    0x0000 00000 (preempt.go:17)    TEXT    "".Test(SB), NOSPLIT|ABIInternal, $0-0

    最感兴趣的是 NOSPLIT 位。
    如果我们查阅 the docs on the Go's assembler ,我们会发现

    For example, here is a simple complete function definition. The TEXT directive declares the symbol <…> and the instructions that follow form the body of the function.

    <…>

    • NOSPLIT = 4
      (For TEXT items.) Don't insert the preamble to check if the stack must be split. The frame for the routine, plus anything it calls, must fit in the spare space at the top of the stack segment. Used to protect routines such as the stack splitting code itself.


    很难理解为什么会这样称呼它并提到“拆分”,但那是从 Go 的 goroutine 有“拆分堆栈”的日子开始的:也就是说,当 goroutine 的堆栈必须增长,并且其中没有可用空间时,另一个“ block ”将被分配给它,并链接到原来的;该过程可能会重复多次——导致多段、拆分、堆栈。由此得名。
    在 Go 开发周期的后期 goroutine stacks became contiguous and reallocatable(很像 slice 的支持数组),但为了向后兼容,保留了这个晦涩的标识符的名称。

    好的,事情是这样的:编译器注意到函数 Test 没有在其堆栈上分配任何东西,因此它永远不会使用超出其自己的堆栈帧(由调用者分配)之外的任何堆栈空间,因此编译器继续并标记该函数为 NOSPLIT 以消除任何运行时检查以增加堆栈的需要 - 以获得调用加速。

    这就是你在运行时观察到的。

    如果您对它感兴趣,您可以通过在其上运行 go tool objdump 来反汇编已构建的可执行镜像。
    它对于分析来说也相当不错,但包含很多杂乱无章的东西,因为链接到的所有运行时、stdlib 和初始化代码都与您自己的代码一起反汇编,您需要在转储中搜索这些代码。

    关于go - 为什么 goroutine 饿死了?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61915917/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com