• goroutine是如何启停的


    写在前面

    本文基于GoLang 1.14

    在Go中,goroutine不过是一个Go结构,其中包含了关于正在运行的程序的信息,如堆栈、程序计数器或其当前的操作系统线程。Go调度器会处理这些信息,给它们提供运行时间。调度器在goroutine的启动和退出时也要注意,这两个阶段需要小心管理。

    启动

    创建一个goroutine非常的简单,如下:

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    func main() {
    	var wg sync.WaitGroup
    	wg.Add(1)
    
    	go func() {
    		fmt.Println("Hello")
    		wg.Done()
    	}()
    
    	fmt.Println("Done")
    	wg.Wait()
    }
    

    主函数在打印消息之前,会启动一个goroutine。由于goroutine会有自己的运行时间,Go会通知运行时建立一个新的goroutine,也就是说:

    • 创建堆栈。
    • 收集当前程序计数器或调用者的数据信息。
    • 更新goroutine的内部数据,如ID或状态。

    但是,goroutine不会立即获得任何运行时间。新创建的goroutine将被放到在本地队列的开头,并在下一轮GoLang调度器中运行。

    把goroutine放在队列的头,目的是使它在当前goroutine之后第一个运行。如果有work-stealing的情况发生,它将在同一线程或另一线程上运行。

    我们可以从下面的汇编代码中看到goroutine的创建:

    一旦goroutine被创建并推送到本地的goroutine队列中,它就直接进入主函数的下一条指令。

    退出

    当一个goroutine结束时,为了不浪费CPU时间,Go必须调度另一个goroutine。同时,它还会保留当前这个goroutine,以便以后重复使用。

    然而,GoLang需要一种方法来感知goroutine的结束。这个控制是在创建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 skInt int
    		for {
    			_, file, line, ok := runtime.Caller(skInt)
    
    			if !ok {
    				break
    			}
    
    			fmt.Printf("%s:%d
    ", file, line)
    			skInt++
    		}
    
    		wg.Done()
    	}()
    
    	fmt.Println("Done")
    	wg.Wait()
    }
    

    运行代码得到如下输出:

    F:hello>go run main.go
    Done
    F:/hello/main.go:16
    D:/Go/src/runtime/asm_amd64.s:1374
    

    asm_amd64.s这个文件中,我们可以看到有如下的函数的定义:

    // The top-most function running on a goroutine
    // returns to goexit+PCQuantum.
    TEXT runtime·goexit(SB),NOSPLIT,$0-0
    	BYTE	$0x90	// NOP
    	CALL	runtime·goexit1(SB)	// does not return
    	// traceback from goexit1 must hit code range of goexit
    	BYTE	$0x90	// NOP
    

    我们可以看到,GoLang将会切换到g0这个goroutine从而去调度其它的goroutine。

    在我们代码中,我们也可以调用runtime.Goexit()去主动退出。

    package main
    
    import (
    	"fmt"
    	"runtime"
    	"sync"
    )
    
    func main() {
    	var wg sync.WaitGroup
    
    	wg.Add(1)
    
    	go func() {
    		defer wg.Done()
    		runtime.Goexit()
    
    		fmt.Println("exist")
    	}()
    
    	wg.Wait()
    }
    

    这个函数将首先运行defer函数,然后当一个goroutine退出时,将调用之前看到的同一个函数。

  • 相关阅读:
    使用MaxCompute Java SDK 执行任务卡住了,怎么办?
    通过编辑文件的方式对DNS服务器进行配置
    2009级计算机应用 嵌入式方向课表
    微软首宗针对中国大企业盗版案宣判:获赔217万
    小數點的運算[討論區- PHP新手區] : 台灣PHP聯盟
    Linux C编程一站式学习
    用wget做站点镜像
    2008级嵌入式方向学生 学习成果(创意)
    Linux领航未来操作系统
    fedora12 微软雅黑
  • 原文地址:https://www.cnblogs.com/double12gzh/p/13658312.html
Copyright © 2020-2023  润新知