• 【Go并发编程】Goroutine的基本使用


    goroutine是什么#

    goroutine即协程,使用go关键字开启一个协程异步执行代码。

    注意,main函数也是个goroutine。

    基本使用#

    使用go执行子任务,会交替执行(和时间片一样)。

    主goroutine退出后,其它的工作goroutine也会自动退出(有点父子进程的感觉):

    Copy Highlighter-hljs
    package main import ( "fmt" "time" ) func newTask() { i := 0 for { i++ fmt.Printf("new goroutine: i = %d\n", i) time.Sleep(1 * time.Second) //延时1s } } func main() { //创建一个 goroutine,启动另外一个任务 go newTask() i := 0 //main goroutine 循环打印 for { i++ fmt.Printf("main goroutine: i = %d\n", i) time.Sleep(1 * time.Second) //延时1s } //这里是加入了死循环,如果去掉,则程序会直接退出。 }

    多个协程的顺序是不一定的。

    Copy Highlighter-hljs
    var _ = runtime.GOMAXPROCS(3) var a, b int func u1() { a = 1 b = 2 } func u2() { a = 3 b = 4 } func p() { println(a) println(b) } func main() { go u1() // 多个 goroutine 的执行顺序不定 go u2() go p() time.Sleep(1 * time.Second) }

    runtime包#

    Gosched#

    runtime.Gosched() //让别人先执行,需要同时需要时间片的时候才会有效,对方如果已经停了就还是自己执行。

    就像孔融让梨(梨就是CPU时间片),A遇到runtime.Gosched()就先给B吃(让出时间片),但是如果B已经吃完了(B已经不需要时间片了),A就开始吃(A则开始占用CPU)。

    Copy Highlighter-hljs
    func main() { //创建一个goroutine go func(s string) { for i := 0; i < 2; i++ { fmt.Println(s) } }("world") for i := 0; i < 2; i++ { runtime.Gosched() //import "runtime" /* 屏蔽runtime.Gosched()运行结果如下: hello hello 没有runtime.Gosched()运行结果如下: world world hello hello */ fmt.Println("hello") } }

    优先调度:

    你的程序可能出现一个 goroutine 在运行时阻止了其他 goroutine 的运行,比如程序中有一个不让调度器运行的 for 循环:

    调度器会在 GC、Go 声明、阻塞 channel、阻塞系统调用和锁操作后再执行,也会在非内联函数调用时执行:

    Copy Highlighter-hljs
    func main() { done := false go func() { done = true }() //这里占用了调度,协程无法启动 for !done { println("not done !") // 并不内联执行 } println("done !") } //可以添加 -m 参数来分析 for 代码块中调用的内联函数 修改: func main() { done := false go func() { done = true }() for !done { runtime.Gosched() } println("done !") }

    Goexit#

    runtime.Goexit() //将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer延迟调用被执行。

    Copy Highlighter-hljs
    package main import ( "fmt" "runtime" "time" ) //调用 runtime.Goexit() 将立即终止当前 goroutine 执⾏,调度器确保所有已注册 defer延迟调用被执行。 func main() { go func() { defer fmt.Println("A.defer") func() { defer fmt.Println("B.defer") runtime.Goexit() // 终止当前 goroutine, import "runtime" fmt.Println("B") // 不会执行 }() defer fmt.Println("C.defer") //还没来得及注册,不会执行 fmt.Println("A") // 不会执行 }() //别忘了() //死循环,目的不让主goroutine结束 for { time.Sleep(1 * time.Second) } } //执行结果: //B.defer //A.defer

    GOMAXPROCS#

    调用 runtime.GOMAXPROCS() 用来设置可以并行计算的CPU核数的最大值,并返回之前的值。

    示例代码:

    Copy Highlighter-hljs
    func main() { //n := runtime.GOMAXPROCS(1) //打印结果:111111111111111111110000000000000000000011111... n := runtime.GOMAXPROCS(2) //打印结果:010101010101010101011001100101011010010100110... fmt.Printf("n = %d\n", n) for { go fmt.Print(0) fmt.Print(1) } }

    在第一次执行(runtime.GOMAXPROCS(1))时,最多同时只能有一个goroutine被执行。所以会打印很多1。

    过了一段时间后,GO调度器会将其置为休眠,并唤醒另一个goroutine,这时候就开始打印很多0了,在打印的时候,goroutine是被调度到操作系统线程上的。

    在第二次执行(runtime.GOMAXPROCS(2))时,我们使用了两个CPU,所以两个goroutine可以一起被执行,以同样的频率交替打印0和1。

  • 相关阅读:
    ChatGPT Plus的Vision升级是一个改变游戏规则的创举
    供应链全流程计划与排产解决方案核心功能概要
    PMP每日十题-2024年2月29日
    穿越时空的创新:解析云原生与Web3.0的奇妙渊源
    强化游戏产业发展 新疆文化和旅游厅代表赴粤交流考察
    利用存储过程造测试数据
    驱动开发:内核字符串拷贝与比较
    HttpContext.TraceIdentifier那严谨的设计
    如何在 Ubuntu 22.04.1 上安装并使用 PostgreSQL 14.5?
    2023高教社杯 国赛数学建模D题。圈养湖羊的空间利用率
  • 原文地址:https://www.cnblogs.com/HappyTeemo/p/17138227.html