- 一个协程必须对应一个方法,可以使用多个协程启动同一个方法
- 程序启动时,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会一同结束

示例,单个协程:

  1. func hello() {
  2. fmt.Println("Hello Goroutine!")
  3. }
  4. func main() {
  5. go hello() // 启动另外一个goroutine去执行hello函数
  6. fmt.Println("main goroutine done!")
  7. //延时一秒,防止进程退出
  8. time.Sleep(1000)
  9. }

执行上面的代码你会发现,这一次先打印main goroutine done!,然后紧接着打印Hello Goroutine!。
首先为什么会先打印main goroutine done!是因为我们在创建新的goroutine的时候需要花费一些时间,而此时main函数所在的goroutine是继续执行的

示例,多个协程:

在Go语言中实现并发就是这样简单,我们还可以启动多个goroutine。让我们再来一个例子: (这里使用了sync.WaitGroup来实现goroutine的同步)

  1. var wg sync.WaitGroup
  2. func hello(i int) {
  3. defer wg.Done() // goroutine结束就登记-1
  4. fmt.Println("Hello Goroutine!", i)
  5. }
  6. func main() {
  7. for i := 0; i < 10; i++ {
  8. wg.Add(1) // 启动一个goroutine就登记+1
  9. go hello(i)
  10. }
  11. wg.Wait() // 等待所有登记的goroutine都结束
  12. }

多次执行上面的代码,会发现每次打印的数字的顺序都不一致。这是因为10个goroutine是并发执行的,而goroutine的调度是随机的。

示例,设置GOMAXPROCS

  1. package main
  2. import (
  3. "math"
  4. "sync"
  5. )
  6. func sum(id int) {
  7. var x int64
  8. for i := 0; i < math.MaxUint32; i++ {
  9. x += int64(i)
  10. }
  11. println(id, x)
  12. }
  13. func main() {
  14. wg := new(sync.WaitGroup)
  15. wg.Add(2)
  16. for i := 0; i < 2; i++ {
  17. go func(id int) {
  18. defer wg.Done()
  19. sum(id)
  20. }(i)
  21. }
  22. wg.Wait()
  23. }
  24. 命令行输入:
  25. go build main.go
  26. time -p ./main
  27. 输出结果:
  28. 0 9223372030412324865
  29. 1 9223372030412324865
  30. real 1.92 // 程序开始到结束时间差 ( CPU 时间)
  31. user 3.80 // 用户态所使用 CPU 时间片 (多核累加)
  32. sys 0.01 // 内核态所使用 CPU 时间片

命令行输入:

  1. GOMAXPROCS=8 time -p ./main

输出结果:

  1. 1 9223372030412324865
  2. 0 9223372030412324865
  3. real 1.89
  4. user 3.76 // 虽然总时间差不多,但由 2 个核并行,real 时间自然少了许多。
  5. sys 0.00

设置golang运行的cpu核数
单核执行如果for前面或者中间不延迟,主线程不会让出CPU,导致异步的线程无法执行,从而无法设置flag的值,从而出现死循环。

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. var (
  7. flag = false
  8. str string
  9. )
  10. func foo() {
  11. flag = true
  12. str = "setup complete!"
  13. }
  14. func main() {
  15. runtime.GOMAXPROCS(1)
  16. go foo()
  17. for {
  18. if flag {
  19. break
  20. }
  21. }
  22. fmt.Println(str)
  23. }

运行的cpu核数设置成2核
runtime.GOMAXPROCS(2)

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. var (
  7. flag = false
  8. str string
  9. )
  10. func foo() {
  11. flag = true
  12. str = "setup complete!"
  13. }
  14. func main() {
  15. runtime.GOMAXPROCS(2)
  16. go foo()
  17. for {
  18. if flag {
  19. break
  20. }
  21. }
  22. fmt.Println(str)
  23. }

输出结果:

  1. setup complete!

runtime包

runtime.Gosched()

让出CPU时间片,重新等待安排任务

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. func main() {
  7. go func(s string) {
  8. for i := 0; i < 2; i++ {
  9. fmt.Println(s)
  10. }
  11. }("world")
  12. // 主协程
  13. for i := 0; i < 2; i++ {
  14. // 切一下,再次分配任务
  15. runtime.Gosched()
  16. fmt.Println("hello")
  17. }
  18. }

runtime.Goexit()

退出当前协程

  1. package main
  2. import (
  3. "fmt"
  4. "runtime"
  5. )
  6. func main() {
  7. go func() {
  8. defer fmt.Println("A.defer")
  9. func() {
  10. defer fmt.Println("B.defer")
  11. // 结束协程
  12. runtime.Goexit()
  13. defer fmt.Println("C.defer")
  14. fmt.Println("B")
  15. }()
  16. fmt.Println("A")
  17. }()
  18. for {
  19. }
  20. }

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逻辑核心上实现并行的效果,这里举个例子:

  1. func a() {
  2. for i := 1; i < 10; i++ {
  3. fmt.Println("A:", i)
  4. }
  5. }
  6. func b() {
  7. for i := 1; i < 10; i++ {
  8. fmt.Println("B:", i)
  9. }
  10. }
  11. func main() {
  12. runtime.GOMAXPROCS(1)
  13. go a()
  14. go b()
  15. time.Sleep(time.Second)
  16. }