非零基础自学Golang

第13章 并发与通道

13.4 select

Go语言中,通过关键字select可以监听channel上的数据流动

select的用法和switch非常相似,由select开始一个新的选择块,每个选择条件由case语句来描述。

13.4.1 select作用

与switch语句可以选择任何可使用相等比较的条件相比,select有较多的限制,其中最大的限制就是每个case语句里面必须是一个I/O操作,大致结构如下:

select {
case <-chan1:
 	// 如果chan1成功读到数据,则执行该case语句
case chan2 <- 1:
 	// 如果成功向chan2写入数据,则执行该case语句
default:
	 //如果上面的case都没有执行成功,则执行该default语句
}

在一个select语句中,Go语言会按顺序从头至尾评估每一个发送和接收语句,如果其中的任意一个语句可以继续执行(没有被阻塞),那么就从那些可以执行的语句中随机选择一条来使用。

如果没有任何一条语句可以执行(即所有通道都被阻塞),就会默认执行default语句,同时程序的执行会从select语句后的语句中恢复。如果没有default语句,则select语句将被阻塞,直到有一个channel可以进行下去。

[ 动手写 13.4.1]

package main

import (
   "fmt"
   "time"
)

func main() {

   ch := make(chan int)

   go func() {

      for i := 0; i < 3; i++ {
         ch <- i
      }
   }()

   for {
      select {
      case msg := <-ch:
         fmt.Println(msg)
      default:
         time.Sleep(time.Second)
      }
   }
}

运行结果

在这里插入图片描述

13.4.2 超时

有时候会出现goroutine阻塞的情况,为了避免程序长时间进入阻塞,我们可以使用select来实现阻塞超时机制,通过如下方法实现:

[动手写 13.4.2 ]

package main

import (
   "fmt"
   "time"
)

func main() {

   ch := make(chan int)

   done := make(chan bool)

   go func() {

      for {
         select {
         case val := <-ch:
            fmt.Println(val)
         case <-time.After(time.Second * 3):
            fmt.Println("已超时3秒")
            done <- true
         }
      }
   }()

   for i := 0; i < 10; i++ {
      ch <- i
   }

   <-done
   fmt.Println("程序终止")
}

运行结果:

在这里插入图片描述

13.4.3 死锁

在编写并发程序时可能会碰到死锁。

什么是死锁?就是所有的线程或进程都在等待资源的释放,举个例子:

[动手写 13.4.3]

package main

func main() {

   ch := make(chan int)
   <-ch // 阻塞main goroutine, 信道ch 被锁
}

在这里插入图片描述

动手写13.4.3中只有一个goroutine,我们向里面加数据或者存数据的话,都会锁死信道,并且阻塞当前goroutine,也就是所有的goroutine(其实只有main线程一个)都在等待信道的开放(没人拿走数据的话,信道是不会开放的),这就产生了死锁。

在非缓冲信道上如果发生了流入无流出,或者流出无流入,都会导致死锁。

同样地,使用select关键字,其中不加任何代码也会产生死锁。

[ 动手写 13.4.4 ]

package main

func main() {

   select {
   
   }
}

运行结果

在这里插入图片描述

13.5 小结

  • 了解并行和并发的区别。
  • 掌握goroutine的创建和使用。
  • 掌握channel的创建和使用,以及多个goroutine之间的通信。
  • 掌握如何使用runtime包对goroutine进行调度。
  • 掌握channel的缓冲机制。
  • 掌握Go关键字select的常见用法。