美文网首页
Go goroutine

Go goroutine

作者: JunChow520 | 来源:发表于2021-03-27 12:12 被阅读0次

Golang特色之一是支持高并发编程模型,以Goroutine作为基本的并发执行单元。

  • Goroutine是轻量级线程
  • Goroutine的调度是由Golang运行时进行管理的。

使用Goroutine

Golang使用go关键字开启Goroutine来支持并发编程,使用go语句会开启一个全新的运行期线程即Goroutine。

在调用函数前添加go关键字即可为函数创建一个Goroutine

go funcName(argumentList)
  • 一个函数可以创建出多个goroutine,而一个goroutine必定对应一个函数。
  • 同一个函数中所有Goroutine将共享同一个地址空间。

例如:默认Go程序从main入口函数开始按照串行方式从上到下依次执行的

在Windows和Linux出现之前的古老年代,程序员在开发时并没有并发的概念,因为命令式程序设计语言是以串行为基础的,程序会顺序执行的每一条指令,整个程序只有一个执行上下文,即一个调用栈和一个堆。

package main

import (
    "fmt"
)

func test() {
    fmt.Println("test goroutine")
}

//程序启动时会创建一个名为main的Goroutine去执行
func main() {
    test()
    fmt.Println("main goroutine")
}
$ go run main.go
test goroutine
main goroutine

例如:创建并发执⾏单元,Go调度器会将其安排到合适的系统线程上去执行。

并发意味着程序在运行时具有多个执行上下文,对应着多个调用栈。由于每个进程在运行时都具有自己的调用栈和堆,因此会拥有一个完整的上下文。而操作系统在调度进程时会保存被调度进程的上下文环境,等待进程获得时间片后,再恢复该进程的上下文到系统中。

package main

import (
    "fmt"
)

func test() {
    fmt.Println("test goroutine")
}

//程序启动时会创建一个名为main的Goroutine去执行
func main() {
    go test() //开启一个单独的Goroutine去执行当前函数
    fmt.Println("main goroutine")
}
$ go run main.go
main goroutine

为什么只输出了main goroutine呢?由于main函数执行到go test(),此时会创建并发任务,而非执行并发操作。新建的并行任务单元会被放置到系统队列中,等待调度器安排合适的系统线程去获取执行权。接着main函数线下执行输出打印main goroutine。执行完毕main函数结束,此时main函数内的goroutine会全部死亡。

形象地看,main函数类似夜王,旗下的Goroutine类似它创建出来的小鬼。夜王一死,所有的小鬼全部跟着倒下。

如何才能让test函数中的内容打印输出呢?让main主函数延迟结束。

package main

import (
    "fmt"
    "time"
)

func test() {
    fmt.Println("test goroutine")
}

//程序启动时会创建一个名为main的Goroutine去执行
func main() {
    //开启一个单独的Goroutine去执行当前函数
    go test()
    fmt.Println("main goroutine")
    //主函数休眠1秒延迟结束等待test函数执行完毕
    time.Sleep(time.Second * 1)
}
$ go run main.go
main goroutine
test goroutine

Go程序启动时会为main()函数创建一个默认的Goroutine即Main Goroutine,当main()函数返回时Main Goroutine结束,所有在main()函数中启动的Goroutine也会一起结束。

Goroutine和线程一样,主函数main()的Goroutine并不会等待其它Goroutine结束。如果main()函数中的Goroutine结束,其下的所有goroutine都将结束。

需要注意的是,函数调用前添加go关键字表示本次调用会在一个全新的Goroutine中并发执行,当被调用的函数return返回时,当前的Work Goroutine也会自动结束。如果函数存在返回值,则返回值会被丢弃。

使用匿名函数创建Goroutine

go关键字可以为匿名函数或闭包启动Goroutine

go func(argumentList) {
  //function body
}(parameters)

例如:并行执行定时打印计数

package main

import (
    "fmt"
    "time"
)

func main() {
    //并发执行计数器
    go func() {
        var count int
        for {
            count++
            fmt.Println("tick", count)
            time.Sleep(time.Second)
        }
    }()
    //接收命令行输入
    var input string
    fmt.Scanln(&input)
}

例如:计数器每秒执行1次的同时等待用户输入

package main

import (
    "fmt"
    "time"
)

//每隔1秒打印依次计数器
func counter() {
    var count int
    //无限循环
    for {
        count++
        fmt.Println("tick", count)
        time.Sleep(time.Second) //延迟1秒
    }
}

func main() {
    //并发执行计数器
    go counter()
    //接收命令行输入
    var input string
    fmt.Scanln(&input)
}

Go程序启动时运行时runtime会默认为main()主函数创建一个goroutine,在main()主函数的goroutine中执行go counter()时,counter()计数器函数的goroutine被创建。counter()函数在自己的goroutine中执行。同时main()主函数依然持续运行。两个goroutine通过Go程序的调度机制同时在运行。

启动多个Goroutine

使用for循环开启多个Goroutine并发执行

package main

import (
    "fmt"
    "time"
)

func test(i int) {
    fmt.Printf("test goroutine %d\n", i)
}

//程序启动时会创建一个名为main的Goroutine去执行
func main() {
    for i := 0; i < 100; i++ {
        go test(i) //开启一个单独的Goroutine去执行当前函数
    }
    fmt.Println("main goroutine")
    //主函数休眠1秒延迟结束等待test函数执行完毕
    time.Sleep(time.Second * 1)
}

多次执行会发现每次执行的顺序都不一致,为什么呢?因为多个Goroutine并发执行时,其调度是随机的。

对于实际应用中,休眠1秒是可能完全不够,因为无法预知for循环内代码运行时间的长短,因此不能简单粗暴地使用time.Sleep()来完成等待操作。

可以通过sync.WaitGroup等待组实现多个任务的同步,sync.WaitGroup可以保证在并发环境中完成指定数量的任务。

package main

import (
    "fmt"
    "sync"
)

//创建等待组
var wg sync.WaitGroup

func test(i int) {
    defer wg.Done() //Goroutine执行完毕则计数-1
    fmt.Printf("test goroutine %d\n", i)
}

//程序启动时会创建一个名为main的Goroutine去执行
func main() {
    for i := 0; i < 100; i++ {
        wg.Add(1)  //启动一个Goroutine登记一个
        go test(i) //开启一个单独的Goroutine去执行当前函数
    }
    fmt.Println("main goroutine")
    //等待所有登记的Goroutine执行完毕
    wg.Wait()
}

相关文章

网友评论

      本文标题:Go goroutine

      本文链接:https://www.haomeiwen.com/subject/hqyszktx.html