参考资料:

并发基础 - 图1

1. 无缓冲channel

【示例】在网球比赛中,两位选手会把球在两个人之间来回传递。选手总是处在以下两种状态之一,要么在等待接球,要么将球打向对方。可以使用两个 goroutine 来模拟网球比赛,并使用无缓冲的通道来模拟球的来回,代码如下所示。
// 这个示例程序展示如何用无缓冲的通道来模拟 // 2 个goroutine 间的网球比赛

  1. package main
  2. import (
  3. "fmt" //标准输入输出
  4. "math/rand" //提供随机数
  5. "sync" //提供同步函数
  6. "time" //提供时间函数
  7. )
  8. var wg sync.WaitGroup //WaitGroup变量
  9. func init() { //总之,要想实现随机数,就得写这个
  10. rand.Seed(time.Now().UnixNano()) //设置随机数种子,保证“每次随机都是随机的”
  11. }
  12. func main() {
  13. court := make(chan int) //传递的球(击打次数)
  14. wg.Add(2) //表示需要等两个goroutine“Done”
  15. go player("Jin", court)
  16. go player("Tom", court)
  17. court <- 1 //“发球”,即给出第一次击打
  18. wg.Wait() //等待两个goroutine打完
  19. }
  20. func player(name string, court chan int) {
  21. defer wg.Done() //defer关键字是在函数要返回之前调用一些操作
  22. for {
  23. hit_num,ok := <- court //得到当前“球”和球的状态
  24. if !ok {
  25. //如果球没打过来,受球方赢
  26. fmt.Printf("player %s Won\n", name)
  27. return
  28. }
  29. n := rand.Intn(100) //生成随机数
  30. if n % 13 == 0 { //随便定义,例如此时,击球方miss
  31. fmt.Printf("player %s Missed\n", name)
  32. //
  33. close(court) //“球掉了”,即关闭channel时它的状态为false
  34. return
  35. }
  36. //以上都没发生,就正常击球,击球次数+1
  37. fmt.Printf("Player %s hit %d\n", name, hit_num)
  38. hit_num++
  39. court <- hit_num //更新channel的值传递给受球方
  40. }
  41. }

2. 有缓冲channel

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. //make(chan 通道类型, 缓冲大小),这里的“缓冲大小”是可接收元素的数量
  7. ch := make(chan int, 4) //可接收 4 个元素
  8. fmt.Println(len(ch)) //输出:0
  9. ch <- 1
  10. ch <- 2
  11. ch <- 3
  12. ch <- 4
  13. fmt.Println(len(ch)) //输出:4
  14. le := len(ch) //输出:1 2 3 4
  15. for i:=0;i<le;i++ {
  16. fmt.Println(<-ch)
  17. }
  18. }

3. channel的超时机制:select

虽然 select 机制不是专门为超时而设计的,却能很方便的解决超时问题,因为 select 的特点是只要其中有一个 case 已经完成,程序就会继续往下执行,而不会考虑其他 case 的情况

  • 与 sw itch 语句相比,select 有比较多的限制,其中最大的一条限制就是每个 case 语句里必须是一个 IO 操作,大致的结构如下:
    1. select {
    2. case <-chan1:
    3. // 如果chan1成功读到数据,则进行该case处理语句
    4. case chan2 <- 1:
    5. // 如果成功向chan2写入数据,则进行该case处理语句
    6. default:
    7. // 如果上面都没有成功,则进入default处理流程
    8. }
    【示例】报数程序: ```go package main

import ( “fmt” “time” )

func main() { ch := make(chan int) //报数 quit := make(chan bool) //结束信号 var t1 time.Time //声明一个时间类型变量

  1. //开一个协程(匿名函数)
  2. go func() {
  3. for {
  4. select {
  5. case num := <-ch: //有数就报数
  6. fmt.Println("num = ", num)
  7. case <-time.After(3 * time.Second): //等待三秒,没有数就报错超时
  8. t2 := time.Now() //获取当前时间,t2.Sub(t1)是t2与t1的时间差
  9. fmt.Println("time out! (wait for", t2.Sub(t1), "seconds)")
  10. quit <- true //传递结束信号
  11. }
  12. }
  13. }()
  14. for i := 0; i < 5; i++ {
  15. time.Sleep(time.Second) //每次报数间隔一秒
  16. if i == 4 {
  17. t1 = time.Now() //获取最后一次报数的时间,用于计算时间差
  18. }
  19. ch <- i
  20. }
  21. <-quit
  22. fmt.Println("Program over.")

}

  1. - 输出:

num = 0 num = 1 num = 2 num = 3 num = 4 time out! (wait for 3.000954194s seconds) Program over.

  1. - 关于time.After(): golang 中,谁也无法保证某些情况下的 select 是否会永久阻塞。很多时候都需要设置一下 select 的超时时间,可以借助 time 包的 After() 实现。
  2. ---
  3. <a name="qScUZ"></a>
  4. ## 4. 在业务中的使用
  5. 举例:
  6. ```go
  7. var wg sync.WaitGroup
  8. for _, val := range arrars {
  9. wg.Add(1) //要做+1
  10. go func() {
  11. //...
  12. wg.Done() //做完1个
  13. }()
  14. }
  15. wg.Wait() //等待全部完成