channel(管道)

87 阅读4分钟

可以使用全局变量加锁同步来解决goroutine的通讯,但并不完美,这就引出了新的通讯机制-channel

channel的基本概念

channel是Go语言中的一种类型,用于在协程之间传递数据。

  1. channel的本质是一种数据结构——队列,数据是FIFO先进先出的。
  2. 线程安全,多协程访问时,不需要加锁,channel本身是线程安全的
  3. channel是有类型的,一个string的channel只能存放string类型的数据
ch := make(chan string)

这样数据就可以从一个协程中创建并流入管道,再从管道流出到另一个协程中。通过这样的方式在协程之间实现通信。

channel的使用方法

  • channel是引用类型
  • channel必须初始化后才能写数据,即make之·后才能使用
package main

import "fmt"

func main() {
   // 创建一个可以存放3个int类型的管道
   intChan := make(chan int, 3)

   fmt.Println("intChan的值:", intChan, "  intChan的地址", &intChan)

   // 向管道内写入数据
   intChan <- 10
   num := 985
   intChan <- num

   // 查看管道的长度和cap容量
   fmt.Println("channel len: ", len(intChan), "  cap: ", cap(intChan))
   
    // 从管道中读取数据
    num2 := <-intChan
    fmt.Println("num2: ", num2)
    fmt.Println("channel len: ", len(intChan), "  cap: ", cap(intChan))

}

channel的容量是有限制的,不能动态增长,是静态的,如果超过这个容量会报deadlock错误。当管道内的数据取完之后,再对空管道进行取数操作也会报deadlock错误。

image.png 也可以直接让元素出管道,不用变量接就好

<-intChan

channel的关闭

使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从channel中读取数据

package main

func main() {
   intChan := make(chan int, 3)
   intChan <- 100
   intChan <- 200
   close(intChan) //close之后就不能再写数据到channel中
   intChan <- 300
}

image.png 当channel关闭后,继续向其中写入数据会导致panic

channel的遍历

channel支持for-range的方式进行遍历

  1. 在遍历时,如果channel没有关闭,则会出现deadlock的错误。因为如果不关闭管道,管道就没有结束标志,程序会等待管道继续填入数据,从而导致死锁。
func main() {
   intChan := make(chan int, 100)
   for i := 0; i < 66; i++ {
      intChan <- i
   }

   // 遍历管道时不能使用普通的for循环
   // 对管道的遍历必须使用for range
   for v := range intChan {
      fmt.Println("v: ", v)
   }
}

image.png 2. 在遍历时,如果channel已经关闭,则可以正常遍历数据。

func main() {
   intChan := make(chan int, 100)
   for i := 0; i < 66; i++ {
      intChan <- i
   }
   close(intChan)
   for v := range intChan {
      fmt.Println("v: ", v)
   }
}

image.png

goroutine和channel搭配使用

// write Data
func writeData(intChan chan int) {
   for i := 0; i < 6; i++ {
      //放入数据
      intChan <- i
      fmt.Println("writeData写了数据: ", i)
      time.Sleep(time.Second)
   }
   close(intChan)
}

// read Data
func readData(intChan chan int, exitChan chan bool) {
   for {
      v, ok := <-intChan
      if !ok {
         break
      }
      fmt.Println("readData读到数据: ", v)
      time.Sleep(time.Second)
   }
   //  读取完数据后,任务完成
   exitChan <- true
   close(exitChan)
}

func main() {
   //创建两个管道
   intChan := make(chan int, 50)
   exitChan := make(chan bool, 1)
   go writeData(intChan)
   go readData(intChan, exitChan)

   for {
      _, ok := <-exitChan
      time.Sleep(time.Second)
      fmt.Println("OK的值为: ", ok)
      if !ok {
         break
      }
   }
}

image.png

channel的阻塞

当向管道内写入的数据量超过了管道的容量时就会发生阻塞而报deadlock错误

func main() {
   intChan := make(chan int, 10)
   for i := 0; i < 50; i++ {
      //放入数据
      intChan <- i
      fmt.Println("writeData写了数据: ", i)
   }
}

image.png

底层的编译器如果发现还有其他协程在读数据时,则不会报死锁错误,但如果没有其他协程在读,而单纯是超过了管道的容量,则会造成死锁。即管道内的数据并非处于流动状态

// write Data
func writeData(intChan chan int) {
   for i := 0; i < 10; i++ {
      //放入数据
      intChan <- i
      fmt.Println("writeData写了数据: ", i)
      time.Sleep(time.Second)
   }
   close(intChan)
}

// read Data
func readData(intChan chan int, exitChan chan bool) {
   for {
      v, ok := <-intChan
      if !ok {
         break
      }
      fmt.Println("readData读到数据: ", v)
      time.Sleep(time.Second)
   }
   //  读取完数据后,任务完成
   exitChan <- true
   close(exitChan)
}

func main() {
   //创建两个管道
   intChan := make(chan int, 5)
   exitChan := make(chan bool, 1)
   go writeData(intChan)
   go readData(intChan, exitChan)

   for {
      _, ok := <-exitChan
      time.Sleep(time.Second)
      fmt.Println("OK的值为: ", ok)
      if !ok {
         break
      }
   }
}

image.png 可以看到,即使管道容量只开了5,但是只要管道处于流动状态,编译器会让他自动阻塞,让管道的数据最终可以写完和读完。