Go 并发编程|关闭 channel 和 for range循环、缓冲channel

5,442 阅读3分钟

引言

v, ok := <- ch中的ok仅表示是否成功读取了channel中的数据,并不代表channel的关闭状态。

for循环的for range形式可用于从通道接收值,直到它关闭为止。

非缓冲channel读取和写入都会阻塞直至另一个goroutine往channel中写入和读取数据,带缓冲的channel只有缓冲区满了,写入会阻塞,缓冲区没有数据读取会阻塞。

关闭通道

发送者可以通过关闭信道,来通知接收方不会有更多的数据被发送到channel上。

close(ch)

接收者可以在接收来自通道的数据时使用额外的变量来检查是否成功读取了数据

语法结构:

v, ok := <- ch  

类似map操作,存储key,value键值对

v,ok := map[key] //根据key从map中获取value,如果key存在, v就是对应的数据,如果key不存在,v是默认值

在上面的语句中,如果ok的值是true,表示成功的从通道中读取了一个数据value。如果ok是false,这意味读取的值是通道类型的零值。

例如,如果通道是一个int通道,那么零值将为0。

示例代码:

func main()  {
   ch1 := make(chan int)
   go sendData(ch1)
   /*
      子goroutine,写出数据10个
              每写一个,阻塞一次,主程序读取一次,解除阻塞

      主goroutine:循环读
              每次读取一个,堵塞一次,子程序,写出一个,解除阻塞

      发送发,关闭通道的--->接收方,接收到的数据是该类型的零值,以及false
   */
   //主程序中获取通道的数据
   for{
      time.Sleep(1*time.Second)
      v, ok := <- ch1 //其他goroutine,显示的调用close方法关闭通道。
      if !ok{
         fmt.Println("已经读取了所有的数据,", v, ok)
         break
      }
      fmt.Println("取出数据:",v, ok)
   }

   fmt.Println("main...over....")
}
func sendData(ch1 chan int)  {
   // 发送方:10条数据
   for i:=0;i<10 ;i++  {
      ch1 <- i//将i写入通道中
   }
   close(ch1) //将ch1通道关闭了。
}

运行结果:

image.png 在上面的程序中,send Goroutine将0到9写入chl通道,然后关闭通道。主函数里有一个无限循环。它检查通道是否在发送数据后,使用变量ok关闭。如果ok是假的,则意味着通道关闭,因此循环结束。还可以打印接收到的值和ok的值。

for range 循环 channel

我们可以循环从通道上获取数据,直到通道关闭。for循环的for range形式可用于从通道接收值,直到它关闭为止。

使用range循环,示例代码:

func main()  {
   ch1 :=make(chan int)
   go sendData(ch1)
   // for循环的for range形式可用于从通道接收值,直到它关闭为止。
   for v := range ch1{
      fmt.Println("读取数据:",v)
   }
   fmt.Println("main..over.....")
}
func sendData(ch1 chan int)  {
   for i:=0;i<10 ; i++ {
      time.Sleep(1*time.Second)
      ch1 <- i
   }
   close(ch1)//通知对方,通道关闭
}

运行结果:

image.png

非缓冲通道

前面讲的所有通道基本上都没有缓冲。发送和接收到一个未缓冲的通道是阻塞的。

一次发送操作对应一次接收操作,对于一个goroutine来讲,它的一次发送,在另一个goroutine接收之前都是阻塞的。同样的,对于接收来讲,在另一个goroutine发送之前,它也是阻塞的。

缓冲通道

缓冲通道就是指一个通道,带有一个缓冲区。发送到一个缓冲通道只有在缓冲区满时才被阻塞。类似地,从缓冲通道接收的信息只有在缓冲区为空时才会被阻塞。

可以通过将额外的容量参数传递给make函数来创建缓冲通道,该函数指定缓冲区的大小。

语法:

ch := make(chan type, capacity)  

上述语法的容量应该大于0,以便通道具有缓冲区。默认情况下,无缓冲通道的容量为0,因此在之前创建通道时省略了容量参数。

示例代码

以下的代码中,channel是带有缓冲区的。

func main() {
   /*
      非缓存通道:make(chan T)
      缓存通道:make(chan T ,size)
          缓存通道,理解为是队列:

      非缓存,发送还是接受,都是阻塞的
      缓存通道,缓存区的数据满了,才会阻塞状态。。

   */
   ch1 := make(chan int)           //非缓存的通道
   fmt.Println(len(ch1), cap(ch1)) //0 0
   //ch1 <- 100//阻塞的,需要其他的goroutine解除阻塞,否则deadlock

   ch2 := make(chan int, 5)        //缓存的通道,缓存区大小是5
   fmt.Println(len(ch2), cap(ch2)) //0 5
   ch2 <- 100                      //
   fmt.Println(len(ch2), cap(ch2)) //1 5

   //ch2 <- 200
   //ch2 <- 300
   //ch2 <- 400
   //ch2 <- 500
   //ch2 <- 600
   fmt.Println("--------------")
   ch3 := make(chan string, 4)
   go sendData3(ch3)
   for {
      time.Sleep(1*time.Second)
      v, ok := <-ch3
      if !ok {
         fmt.Println("读完了,,", ok)
         break
      }
      fmt.Println("\t读取的数据是:", v)
   }

   fmt.Println("main...over...")
}

func sendData3(ch3 chan string) {
   for i := 0; i < 10; i++ {
      ch3 <- "数据" + strconv.Itoa(i)
      fmt.Println("子goroutine,写出第", i, "个数据")
   }
   close(ch3)
}

运行结果:

image.png