程序员社区

关于Goroutine的启动和退出

【译文】原文地址
本文是基于Go 1.14版本
在Go中,goroutine只不过是一个包含正在运行的程序信息的go结构体,例如栈、程序计数器和当前操作系统线程。Go调度器处理这些信息,为它们提供运行时间。调度器还必须负责goroutine的启动和退出,这两个阶段必须谨慎的管理。

关于堆栈和程序计数器的更多内容,建议阅读Goroutine切换实际包含哪些

Go启动

启动一个goroutine十分简单,以下一段程序为例:

package main

import "sync"

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        println("goroutine is running...")
        wg.Done()
    }()
    println("main is running")
    wg.Wait()
}

main函数在打印一条消息之前启动一个goroutine。因为goroutine有自己的运行时,Go通知运行时建立一个新的goroutine,具体执行:

  • 创建栈
  • 收集关于当前程序计数器或者调用者数据信息。
  • 更新goroutine的内部数据,例如ID或状态。
    然而,goroutine并不会立即获得执行。新创建的goroutine将在本地队列开始处进入队列,并在Go调度器的下一轮运行。如下是一个当前运行状态:

    关于Goroutine的启动和退出插图
    Goroutine调度

    将goroutine放在队列的头,使其成为当前goroutine之后第一个运行的goroutine。它将运行在当前线程或另一个线程中,如果发生了抢占调度。
    关于更多goroutine抢占调度内容,可参考Go调度器的抢占调度
    Goroutine的创建也可以在汇编指令当中看到:

    关于Goroutine的启动和退出插图1
    汇编关于Goroutine的创建

    一旦goroutine被创建并push到本地调度队列当中,程序将直接进入main函数的下一条指令。

Goroutine的退出

当一个goroutine执行结束时,Go必须调度其他的goroutine,避免浪费CPU时间。将会保留goroutine以便后面重用。
可以阅读更多关于goroutine回收内容:Go: How Does Go Recycle Goroutines?
然而,Go需要一种方法来感知goroutine的结束。这种感知机制是在创建goroutine的时候,Go在将goroutine调用的函数压栈之前会将一个goexit函数压栈。这种机制可以实现在goroutine调用的函数结束之后会立即执行goexit函数。我们可以通过以下程序来可视化该过程:

package main

import (
    "fmt"
    "runtime"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)

    go func() {
        var skip int
        for {
            _, file, line, ok := runtime.Caller(skip)
            if !ok{
                break
            }
            fmt.Printf("%s:%d\n", file, line)
            skip++
        }
        wg.Done()
    }()
    wg.Wait()
}

output:

C:/Users/Administrator/go/src/vault/main.go:16
C:/Go/src/runtime/asm_amd64.s:1357

以汇编方式编写的文件asm_amd64包含如下函数:

关于Goroutine的启动和退出插图2

然后Go将切换到g0来调度其他的goroutine。也可以通过runtime.Goexit()函数来手动终止goroutine的执行。

package main

import (
    "runtime"
    "sync"
)

func main() {
    var wg sync.WaitGroup
    wg.Add(1)
    go func() {
        defer wg.Done()
        runtime.Goexit()// goroutine exits here
        println("never executed")
    }()
    wg.Wait()
}

这个函数将首先执行defer函数,然后在goroutine退出前调用goexit函数。

赞(0) 打赏
未经允许不得转载:IDEA激活码 » 关于Goroutine的启动和退出

一个分享Java & Python知识的社区