1、创建两个goroutine,以并发的形式分别显示大写和小写字母

  1. // 该程序展示如何创建 goroutine ,以及调度器的行为
  2. package main
  3. import (
  4. "fmt"
  5. "runtime"
  6. "sync"
  7. )
  8. func main() {
  9. // 分配一个逻辑处理器给调度器使用
  10. runtime.GOMAXPROCS(1)
  11. // wg 用来等待程序完成
  12. // 计数加2,表示要等待两个 goroutine
  13. var wg sync.WaitGroup
  14. wg.Add(2)
  15. fmt.Println("Start Groutine")
  16. // 声明一个匿名函数,并创建一个 goroutine
  17. go func() {
  18. // 在函数退出时调用Done来通知main函数工作已经完成
  19. defer wg.Done()
  20. // 显示字母表3次
  21. for i := 0; i < 3; i++ {
  22. for char := 'a'; char < 'a'+26; char++ {
  23. fmt.Printf("%c ", char)
  24. }
  25. fmt.Println()
  26. }
  27. }()
  28. // 声明一个匿名函数,并创建一个 goroutine
  29. go func() {
  30. // 在函数退出时调用Done来通知main函数工作已经完成
  31. defer wg.Done()
  32. // 显示字母表3次
  33. for i := 0; i < 3; i++ {
  34. for char := 'A'; char < 'A'+26; char++ {
  35. fmt.Printf("%c ", char)
  36. }
  37. fmt.Println()
  38. }
  39. }()
  40. // 等待 goroutine 结束
  41. fmt.Println("Waiting To Finish")
  42. wg.Wait()
  43. fmt.Println("Terminating Program")
  44. }
  45. /*
  46. Start Groutine
  47. Waiting To Finish
  48. A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  49. A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  50. A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
  51. a b c d e f g h i j k l m n o p q r s t u v w x y z
  52. a b c d e f g h i j k l m n o p q r s t u v w x y z
  53. a b c d e f g h i j k l m n o p q r s t u v w x y z
  54. Terminating Program
  55. */

2、goroutine 并发求素数

  1. // 求素数
  2. // 该程序展示 goroutine 调度器是如何在单个线程上切分时间片的
  3. package main
  4. import (
  5. "fmt"
  6. "runtime"
  7. "sync"
  8. )
  9. // wg 用来等待程序完成
  10. var wg sync.WaitGroup
  11. func main() {
  12. // 分配一个逻辑处理器给调度器使用
  13. runtime.GOMAXPROCS(1)
  14. // 计数加2,表示要等待两个 goroutine
  15. wg.Add(2)
  16. // 创建两个 goroutine
  17. fmt.Println("Create Goroutine")
  18. go printPrime("A")
  19. go printPrime("B")
  20. // 等待 goroutine 结束
  21. fmt.Println("Waiting To Finish")
  22. wg.Wait()
  23. fmt.Println("Terminating Program")
  24. }
  25. // printPrime 显示5000以内的素数
  26. func printPrime(prefix string) {
  27. // 在函数退出时调用Done来通知main函数工作已经完成
  28. defer wg.Done()
  29. next:
  30. for outer := 2; outer < 5000; outer++ {
  31. for inner := 2; inner < outer; inner++ {
  32. if outer%inner == 0 {
  33. continue next
  34. }
  35. }
  36. fmt.Printf("%s: %d \n", prefix, outer)
  37. }
  38. fmt.Println("Completed", prefix)
  39. }

3、协程并行 + 并发(设置两个逻辑处理器给调度器使用)

  1. // 并行 + 并发
  2. // 该程序展示如何创建 goroutine ,以及调度器的行为
  3. package main
  4. import (
  5. "fmt"
  6. "runtime"
  7. "sync"
  8. "time"
  9. )
  10. func main() {
  11. // 分配 2 个逻辑处理器给调度器使用
  12. runtime.GOMAXPROCS(2)
  13. // wg 用来等待程序完成
  14. // 计数加2,表示要等待两个 goroutine
  15. var wg sync.WaitGroup
  16. wg.Add(2)
  17. fmt.Println("Start Groutine")
  18. // 声明一个匿名函数,并创建一个 goroutine
  19. go func() {
  20. // 在函数退出时调用Done来通知main函数工作已经完成
  21. defer wg.Done()
  22. // 显示字母表3次
  23. for i := 0; i < 3; i++ {
  24. for char := 'a'; char < 'a'+26; char++ {
  25. fmt.Printf("%c ", char)
  26. time.Sleep(time.Millisecond * 10)
  27. }
  28. }
  29. }()
  30. // 声明一个匿名函数,并创建一个 goroutine
  31. go func() {
  32. // 在函数退出时调用Done来通知main函数工作已经完成
  33. defer wg.Done()
  34. // 显示字母表3次
  35. for i := 0; i < 3; i++ {
  36. for char := 'A'; char < 'A'+26; char++ {
  37. fmt.Printf("%c ", char)
  38. }
  39. }
  40. }()
  41. // 等待 goroutine 结束
  42. fmt.Println("Waiting To Finish")
  43. wg.Wait()
  44. fmt.Println("\nTerminating Program")
  45. }
  46. // 两个协程,时分复用,时间切片,交替执行
  47. /*
  48. Start Groutine
  49. Waiting To Finish
  50. a A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z A B C D E F G H I J K L
  51. M N O P Q R S T U V W X Y Z b c d e f g h i j k l m n o p q r s t u v w x y z a b c d e f g h i j k l m n o p q r s t u v w x y z
  52. a b c d e f g h i j k l m n o p q r s t u v w x y z
  53. Terminating Program
  54. */

4、协程竞争状态

  1. // 该程序展示竞争状态(实际中不希望出现这种状态)
  2. package main
  3. import (
  4. "fmt"
  5. "runtime"
  6. "sync"
  7. )
  8. var (
  9. // counter 是所有 goroutine 都要增加其值的变量
  10. counter int
  11. // wg 用来等待程序结束
  12. wg sync.WaitGroup
  13. )
  14. func main() {
  15. wg.Add(2)
  16. go incCounter(1)
  17. go incCounter(2)
  18. wg.Wait()
  19. fmt.Println("Final Counter:", counter)
  20. }
  21. // incCounter 增加报里 counter 变量的值
  22. func incCounter(id int) {
  23. defer wg.Done()
  24. for i := 0; i < 2; i++ {
  25. // 捕获 counter 的值
  26. value := counter
  27. // 当前 goroutine 从线程退出,并放回到队列
  28. runtime.Gosched()
  29. // 增加本地 value 的值, 并将值保存回 counter
  30. value++
  31. counter = value
  32. }
  33. }
  34. // Final Counter: 2
  1. go build -race // 用竞争检测器标志来编译程序
  2. ./main.exe // 运行程序
  3. PS D:\go-project\src\go_code\channel\channel09\main> go build -race
  4. PS D:\go-project\src\go_code\channel\channel09\main> .\main.exe
  5. ==================
  6. WARNING: DATA RACE
  7. Read at 0x000000a29390 by goroutine 8:
  8. main.incCounter()
  9. D:/go-project/src/go_code/channel/channel09/main/main.go:34 +0x8d
  10. main.main·dwrap·2()
  11. D:/go-project/src/go_code/channel/channel09/main/main.go:21 +0x39
  12. Previous write at 0x000000a29390 by goroutine 7:
  13. main.incCounter()
  14. D:/go-project/src/go_code/channel/channel09/main/main.go:41 +0xaa
  15. main.main·dwrap·1()
  16. D:/go-project/src/go_code/channel/channel09/main/main.go:20 +0x39
  17. Goroutine 8 (running) created at:
  18. main.main()
  19. D:/go-project/src/go_code/channel/channel09/main/main.go:21 +0xbc
  20. Goroutine 7 (finished) created at:
  21. main.main()
  22. D:/go-project/src/go_code/channel/channel09/main/main.go:20 +0x7a
  23. ==================
  24. Final Counter: 4
  25. Found 1 data race(s)
  26. PS D:\go-project\src\go_code\channel\channel09\main>

5、消除竞争状态方法

5.1 锁住共享资源

对共享资源加锁,atomic sync 包里的函数提供了很好的解决方案

  1. 原子函数能够以很底层的加锁机制来同步访问整形变量和指针。 ```go // atomic 包 原子函数加锁 package main

import ( “fmt” “runtime” “sync” “sync/atomic” )

var ( // counter 是所有 goroutine 都要增加其值的变量 counter int64 // wg 用来等待程序结束 wg sync.WaitGroup )

func main() { wg.Add(2)

  1. go incCounter(1)
  2. go incCounter(2)
  3. wg.Wait()
  4. fmt.Println("Final Counter:", counter)

}

// incCounter 增加报里 counter 变量的值 func incCounter(id int) { defer wg.Done()

  1. for i := 0; i < 2; i++ {
  2. // 安全的对 counter + 1
  3. // AddInt64() 同步整型值的加法
  4. atomic.AddInt64(&counter, 1)
  5. // 当前 goroutine 从线程退出,并放回到队列
  6. runtime.Gosched()
  7. }

}

// Final Counter: 4

  1. 2. 互斥锁(mutual exclusion, mutex
  2. 互斥锁用于在代码上创建一个临界区,保证同一时间只有一个 goroutine 可以执行临界区的代码。
  3. ```go
  4. // 互斥锁定义同步访问资源的代码临界区
  5. package main
  6. import (
  7. "fmt"
  8. "runtime"
  9. "sync"
  10. )
  11. var (
  12. // counter 是所有 goroutine 都要增加其值的变量
  13. counter int
  14. // wg 用来等待程序结束
  15. wg sync.WaitGroup
  16. // mutex 用来定义一段代码临界区, (加锁、解锁)
  17. mutex sync.Mutex
  18. )
  19. func main() {
  20. wg.Add(2)
  21. go incCounter(1)
  22. go incCounter(2)
  23. wg.Wait()
  24. fmt.Println("Final Counter:", counter)
  25. }
  26. // incCounter 增加报里 counter 变量的值
  27. func incCounter(id int) {
  28. defer wg.Done()
  29. for i := 0; i < 2; i++ {
  30. // 同一时刻,只能允许一个 goroutine 进入这个临界区
  31. mutex.Lock()
  32. {
  33. // 捕获 counter 的值
  34. value := counter
  35. // 当前 goroutine 从线程退出,并放回到队列
  36. runtime.Gosched()
  37. // 增加本地 value 的值, 并将值保存回 counter
  38. value++
  39. counter = value
  40. }
  41. // 释放锁,允许其它正在等待的 goroutine 进入临界区
  42. mutex.Unlock()
  43. }
  44. }
  45. // Final Counter: 4

5.2 通道 channel

在 Go 语言里,你不仅可以使用原子函数和互斥锁来保证对共享资源的安全访问以及消除竞争状态,还可以使用通道,通过发送和接收需要共享的资源,在 goroutine 之间做同步

  1. // 无缓冲的整形通道
  2. unbuffered := make(chan int)
  3. // 有缓冲的字符串通道
  4. buffered := make(chan string, 10)
  1. // goroutine + channel 用共享的方式,消除竞争状态
  2. package main
  3. import (
  4. "fmt"
  5. "runtime"
  6. "sync"
  7. )
  8. var (
  9. // counter 是所有 goroutine 都要增加其值的变量
  10. counter chan int = make(chan int, 1) // 缓冲1个数据的通道,goroutine 更新其值
  11. // wg 用来等待程序结束
  12. wg sync.WaitGroup
  13. )
  14. func main() {
  15. wg.Add(2)
  16. go incCounter(1)
  17. go incCounter(2)
  18. counter <- 0
  19. wg.Wait()
  20. close(counter)
  21. for v := range counter {
  22. fmt.Println("Final Counter:=", v)
  23. }
  24. fmt.Println("Final Counter:", counter)
  25. }
  26. // incCounter 增加报里 counter 变量的值
  27. func incCounter(id int) {
  28. defer wg.Done()
  29. for i := 0; i < 2; i++ {
  30. // 从通道取值 counter 的值
  31. value, ok := <-counter
  32. if !ok {
  33. fmt.Println("err")
  34. return
  35. }
  36. // 当前 goroutine 从线程退出,并放回到队列
  37. runtime.Gosched()
  38. // 增加本地 value 的值, 写入 通道 counter
  39. value++
  40. counter <- value
  41. }
  42. }
  43. // Final Counter:= 4
  44. // Final Counter: 0xc00001a0e0

6、无缓冲的通道模拟网球比赛

  1. // 无缓冲的通道模拟网球比赛
  2. package main
  3. import (
  4. "fmt"
  5. "math/rand"
  6. "sync"
  7. "time"
  8. )
  9. var (
  10. // wg 用来等待程序结束
  11. wg sync.WaitGroup
  12. )
  13. func init() {
  14. rand.Seed(time.Now().UnixNano())
  15. }
  16. func main() {
  17. // 创建一个无缓冲的通道
  18. court := make(chan int)
  19. wg.Add(2)
  20. // 启动两个选手
  21. go player("Nadal", court)
  22. go player("Dav", court)
  23. // 发球
  24. court <- 1
  25. // 等待游戏结束
  26. wg.Wait()
  27. }
  28. func player(name string, court chan int) {
  29. defer wg.Done()
  30. for {
  31. // 等待球被击打过来
  32. ball, ok := <-court
  33. if !ok {
  34. // 如果通道被关闭,我们就赢了
  35. fmt.Printf("Player %s Won \n", name)
  36. return
  37. }
  38. // 选随机数,然后用这个数来判断我们是否丢球
  39. n := rand.Intn(100)
  40. if n%13 == 0 {
  41. fmt.Printf("Player %s Missed❗ \n", name)
  42. // 关闭通道,表示输了
  43. close(court)
  44. return
  45. }
  46. // 显示击球数,并将击球数 +1
  47. fmt.Printf("Player %s Hit %d \n", name, ball)
  48. ball++
  49. // 将球击向对手
  50. court <- ball
  51. }
  52. }
  53. /*
  54. Player Dav Hit 1
  55. Player Nadal Hit 2
  56. Player Dav Missed❗
  57. Player Nadal Won
  58. */

7、无缓冲的通道模拟4个 goroutine 间的接力比赛

  1. // 无缓冲的通道模拟4个 goroutine 间的接力比赛
  2. package main
  3. import (
  4. "fmt"
  5. "sync"
  6. "time"
  7. )
  8. var (
  9. // wg 用来等待程序结束
  10. wg sync.WaitGroup
  11. )
  12. func main() {
  13. // 创建一个无缓冲的通道
  14. baton := make(chan int)
  15. // 为最后一位跑步者将计数 +1
  16. wg.Add(1)
  17. // 第一位跑步者持有接力棒
  18. go Runner(baton)
  19. // 开始比赛
  20. baton <- 1
  21. // 等待游戏结束
  22. wg.Wait()
  23. }
  24. func Runner(baton chan int) {
  25. var newRunner int
  26. // 等待接力棒
  27. runner := <-baton
  28. // 开始跑
  29. fmt.Printf("Runner %d Running Width Baton \n", runner)
  30. // 创建下一位跑步者
  31. if runner != 4 {
  32. newRunner = runner + 1
  33. fmt.Printf("Runner %d To The Line🏃‍♂️ \n", newRunner)
  34. go Runner(baton)
  35. }
  36. // 围绕跑道跑
  37. time.Sleep(100 * time.Microsecond)
  38. // 比赛结束了吗?
  39. if runner == 4 {
  40. fmt.Printf("Runner %d Finished, Race Over🎈\n", runner)
  41. wg.Done()
  42. return
  43. }
  44. // 将接力棒交给下一位跑步者
  45. fmt.Printf("Runner %d Exchange Width Runner %d👉 \n", runner, newRunner)
  46. baton <- newRunner
  47. }
  48. /*
  49. Runner 1 Running Width Baton
  50. Runner 2 To The Line🏃‍♂️
  51. Runner 1 Exchange Width Runner 2👉
  52. Runner 2 Running Width Baton
  53. Runner 3 To The Line🏃‍♂️
  54. Runner 2 Exchange Width Runner 3👉
  55. Runner 3 Running Width Baton
  56. Runner 4 To The Line🏃‍♂️
  57. Runner 3 Exchange Width Runner 4👉
  58. Runner 4 Running Width Baton
  59. Runner 4 Finished, Race Over🎈
  60. */

8、固定数目的有缓冲的通道

  1. // 有缓冲的通道 固定数目
  2. package main
  3. import (
  4. "fmt"
  5. "math/rand"
  6. "sync"
  7. "time"
  8. )
  9. const (
  10. numberGoroutines = 4 // 要使用的 goroutine 的数量
  11. taskLoad = 10 // 要处理的工作的数量
  12. )
  13. var (
  14. // wg 用来等待程序结束
  15. wg sync.WaitGroup
  16. )
  17. func init() {
  18. rand.Seed(time.Now().UnixNano())
  19. }
  20. func main() {
  21. // 创建一个有缓冲的通道来管理工作
  22. tasks := make(chan string, taskLoad)
  23. // 启动 goroutine 来处理工作
  24. wg.Add(numberGoroutines)
  25. for gr := 0; gr < numberGoroutines; gr++ {
  26. go worker(tasks, gr)
  27. }
  28. // 增加一组要完成的工作
  29. for post := 1; post <= taskLoad; post++ {
  30. tasks <- fmt.Sprintf("Task : %d", post)
  31. }
  32. // 当所有工作都处理完时关闭通道
  33. // 以便所有的 goroutine 退出
  34. close(tasks)
  35. // 等待所有工作完成
  36. wg.Wait()
  37. }
  38. func worker(tasks chan string, worker int) {
  39. defer wg.Done()
  40. for {
  41. // 等待分配工作
  42. task, ok := <-tasks
  43. if !ok {
  44. // 意味着通道已经空了,并且已被关闭
  45. fmt.Printf("Worker: %d : Shutting Down✔ \n", worker)
  46. return
  47. }
  48. // 显示我们开始工作了
  49. fmt.Printf("Worker: %d : Started %s \n", worker, task)
  50. // 随机等一段时间来模拟工作
  51. sleep := rand.Int63n(100)
  52. time.Sleep(time.Duration(sleep) * time.Millisecond)
  53. // 显示我们完成了工作
  54. fmt.Printf("Worker: %d : Completed %s \n", worker, task)
  55. }
  56. }
  57. /*
  58. Worker: 3 : Started Task : 1
  59. Worker: 1 : Started Task : 2
  60. Worker: 0 : Started Task : 3
  61. Worker: 2 : Started Task : 4
  62. Worker: 3 : Completed Task : 1
  63. Worker: 3 : Started Task : 5
  64. Worker: 0 : Completed Task : 3
  65. Worker: 0 : Started Task : 6
  66. Worker: 1 : Completed Task : 2
  67. Worker: 1 : Started Task : 7
  68. Worker: 2 : Completed Task : 4
  69. Worker: 2 : Started Task : 8
  70. Worker: 2 : Completed Task : 8
  71. Worker: 2 : Started Task : 9
  72. Worker: 1 : Completed Task : 7
  73. Worker: 1 : Started Task : 10
  74. Worker: 2 : Completed Task : 9
  75. Worker: 2 : Shutting Down✔
  76. Worker: 1 : Completed Task : 10
  77. Worker: 1 : Shutting Down✔
  78. Worker: 3 : Completed Task : 5
  79. Worker: 3 : Shutting Down✔
  80. Worker: 0 : Completed Task : 6
  81. Worker: 0 : Shutting Down✔
  82. */