- 一个协程必须对应一个方法,可以使用多个协程启动同一个方法
- 程序启动时,Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了,
所有在main()函数中启动的goroutine会一同结束
- 默认情况下,进程启动后仅允许一个系统线程服务于 goroutine。可使用环境变量或标准库函数 runtime.GOMAXPROCS 修改,
让调度器用多个线程实现多核并行,而不仅仅是并发,默认值为当前机器的核心数
介绍:
Golang 在语言层面对并发编程提供支持,一种类似协程,称作 goroutine 的机制。
只需在函数调用语句前添加 go 关键字,就可创建并发执行单元。开发人员无需了解任何执行细节,调度器会自动将其安排到合适的系统线程上执行。goroutine 是一种非常轻量级的实现,可在单个进程里执行成千上万的并发任务。
事实上,入口函数 main 就以 goroutine 运行。另有与之配套的 channel 类型,用以实现 “以通讯来共享内存” 的 CSP 模式。
goroutine的使用
在需要并发执行的方法前面加上go关键字,就可以启动一个协程执行该方法,一个协程必须对应一个方法,可以使用多个协程启动同一个方法
需要注意的是,如果在程序启动时,Go程序就会为main()函数创建一个默认的goroutine。当main()函数返回的时候该goroutine就结束了,所有在main()函数中启动的goroutine会一同结束
示例,单个协程:
func hello() {fmt.Println("Hello Goroutine!")}func main() {go hello() // 启动另外一个goroutine去执行hello函数fmt.Println("main goroutine done!")//延时一秒,防止进程退出time.Sleep(1000)}
执行上面的代码你会发现,这一次先打印main goroutine done!,然后紧接着打印Hello Goroutine!。
首先为什么会先打印main goroutine done!是因为我们在创建新的goroutine的时候需要花费一些时间,而此时main函数所在的goroutine是继续执行的
示例,多个协程:
在Go语言中实现并发就是这样简单,我们还可以启动多个goroutine。让我们再来一个例子: (这里使用了sync.WaitGroup来实现goroutine的同步)
var wg sync.WaitGroupfunc hello(i int) {defer wg.Done() // goroutine结束就登记-1fmt.Println("Hello Goroutine!", i)}func main() {for i := 0; i < 10; i++ {wg.Add(1) // 启动一个goroutine就登记+1go hello(i)}wg.Wait() // 等待所有登记的goroutine都结束}
多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。
示例,设置GOMAXPROCS
package mainimport ("math""sync")func sum(id int) {var x int64for i := 0; i < math.MaxUint32; i++ {x += int64(i)}println(id, x)}func main() {wg := new(sync.WaitGroup)wg.Add(2)for i := 0; i < 2; i++ {go func(id int) {defer wg.Done()sum(id)}(i)}wg.Wait()}命令行输入:go build main.gotime -p ./main输出结果:0 92233720304123248651 9223372030412324865real 1.92 // 程序开始到结束时间差 ( CPU 时间)user 3.80 // 用户态所使用 CPU 时间片 (多核累加)sys 0.01 // 内核态所使用 CPU 时间片
命令行输入:
GOMAXPROCS=8 time -p ./main
输出结果:
1 92233720304123248650 9223372030412324865real 1.89user 3.76 // 虽然总时间差不多,但由 2 个核并行,real 时间自然少了许多。sys 0.00
设置golang运行的cpu核数
单核执行如果for前面或者中间不延迟,主线程不会让出CPU,导致异步的线程无法执行,从而无法设置flag的值,从而出现死循环。
package mainimport ("fmt""runtime")var (flag = falsestr string)func foo() {flag = truestr = "setup complete!"}func main() {runtime.GOMAXPROCS(1)go foo()for {if flag {break}}fmt.Println(str)}
运行的cpu核数设置成2核
runtime.GOMAXPROCS(2)
package mainimport ("fmt""runtime")var (flag = falsestr string)func foo() {flag = truestr = "setup complete!"}func main() {runtime.GOMAXPROCS(2)go foo()for {if flag {break}}fmt.Println(str)}
输出结果:
setup complete!
runtime包
runtime.Gosched()
让出CPU时间片,重新等待安排任务
package mainimport ("fmt""runtime")func main() {go func(s string) {for i := 0; i < 2; i++ {fmt.Println(s)}}("world")// 主协程for i := 0; i < 2; i++ {// 切一下,再次分配任务runtime.Gosched()fmt.Println("hello")}}
runtime.Goexit()
退出当前协程
package mainimport ("fmt""runtime")func main() {go func() {defer fmt.Println("A.defer")func() {defer fmt.Println("B.defer")// 结束协程runtime.Goexit()defer fmt.Println("C.defer")fmt.Println("B")}()fmt.Println("A")}()for {}}
runtime.GOMAXPROCS
Go运行时的调度器使用GOMAXPROCS参数来确定需要使用多少个OS线程来同时执行Go代码。默认值是机器上的CPU核心数。例如在一个8核心的机器上,调度器会把Go代码同时调度到8个OS线程上(GOMAXPROCS是m:n调度中的n)。
Go语言中可以通过runtime.GOMAXPROCS()函数设置当前程序并发时占用的CPU逻辑核心数。
Go1.5版本之前,默认使用的是单核心执行。Go1.5版本之后,默认使用全部的CPU逻辑核心数。
我们可以通过将任务分配到不同的CPU逻辑核心上实现并行的效果,这里举个例子:
func a() {for i := 1; i < 10; i++ {fmt.Println("A:", i)}}func b() {for i := 1; i < 10; i++ {fmt.Println("B:", i)}}func main() {runtime.GOMAXPROCS(1)go a()go b()time.Sleep(time.Second)}
