目录
一、创建 goroutine
Go 程序中使用 go 关键字为一个函数创建一个 goroutine。一个函数可以被创建多个 goroutine,一个 goroutine 必定对应一个函数。
go 函数名( 参数列表 )
使用 go 关键字创建 goroutine 时,被调用函数的返回值会被忽略。如果需要在 goroutine 中返回数据,就要使用通道(channel)特性,通过通道把数据从 goroutine 中作为返回值传出。
1、启动单个协程
func running() {
fmt.Println("Hello Goroutine!")
}
func main() {
go running() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
}
程序在启动时,运行时(runtime)会默认为 main() 函数创建一个 goroutine。在 main() 函数的 goroutine 中执行到 go running 语句时,归属于 running() 函数的 goroutine 被创建,running() 函数开始在自己的 goroutine 中执行。此时,main() 继续执行,两个 goroutine 通过 Go 程序的调度机制同时运作。
2、使用goroutine的问题
这一次的执行结果只打印了main goroutine done!,并没有打印Hello Goroutine!。为什么呢?
1)在程序启动时,Go程序就会为main()函数创建一个默认的goroutine(主协程)。
2)当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束
所以我们要想办法让主协程等等其他协程,最简单粗暴的方式就是time.Sleep了。
func main() {
go hello() // 启动另外一个goroutine去执行hello函数
fmt.Println("main goroutine done!")
time.Sleep(time.Second) //停顿一秒,这种方法不好,见channel阻塞/死锁详解
}
3、启动多个goroutine
var wg sync.WaitGroup
func hello(i int) {
defer wg.Done() // goroutine结束就登记-1
fmt.Println("Hello Goroutine!", i)
}
func main() {
for i := 0; i < 10; i++ {
wg.Add(1) // 启动一个goroutine就登记+1
go hello(i)
}
wg.Wait() // 等待所有登记的goroutine都结束
}
多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。
二、使用匿名函数创建goroutine
go 关键字后也可以为匿名函数或闭包启动 goroutine。
go func( 参数列表 ){
函数体
}( 调用参数列表 )
在 main() 函数中创建一个匿名函数并为匿名函数启动 goroutine。匿名函数没有参数。
package main
import (
"fmt"
"time"
)
func main() {
go func() {
var times int
for {
times++
fmt.Println("tick", times)
time.Sleep(time.Second)
}
}()
var input string
fmt.Scanln(&input)
}
总结:
1)所有 goroutine 在 main() 函数结束时会一同结束
2)goroutine 虽然类似于线程概念,但是从调度性能上没有线程细致,而细致程度取决于 Go 程序的 goroutine 调度器的实现和运行环境