package main import "fmt" func main() { // 定义一个名为intChan的管道,该管道只能接收int64类型数据,且容量为4(即最多接收4个int64类型数据) intChan := make(chan int64, 4) // 等价于:var intChan chan int64 = make(chan int64, 4) fmt.Printf("intChan = %+v \n", intChan) // intChan = 0xc0000a4000 重要说明:管道可以看成是一个指针 // 查询管道的长度和容量(由于还没向管道发送任何数据,此时长度为0) fmt.Printf("intChan的长度为:%d,容量为:%d \n", len(intChan), cap(intChan)) // intChan的长度为:0,容量为:4 // 向管道发送数据 intChan <- 1 // 发送第1个数据 intChan <- 0 // 发送第2个数据 intChan <- 2 // 发送第3个数据 intChan <- 4 // 发送第4个数据(此时长度已达到容量值,再向管道发送数据就会报错) // 下面这行代码是不能执行的,因为上面向管道发送4个数据已经达到容量值,再向管道发送数据就会报fatal error // intChan <- 9527 // fatal error: all goroutines are asleep - deadlock! // 重要说明:这里务必要理解,报fatal error并不是因为向已写满数据的管道发送数据,而是程序发生了死锁(deadlock)。 // 向已写满数据的管道发送数据是不会报错的,只是由于没有可用空间存放新数据,发送数据操作会被一直阻塞, // 直到有数据被取出腾出空间存放新数据才会解除阻塞,但是程序到目前为止都没有设置数据接收方来取出数据, // 所以程序会一直阻塞在这里无法继续执行,这才是报fatal error的本质。 // 查询管道的长度和容量(由于上面向管道发送了4个数据,故长度为4) fmt.Printf("intChan的长度为:%d,容量为:%d \n", len(intChan), cap(intChan)) // intChan的长度为:4,容量为:4 // 从管道取出数据(重要提醒:按照先进先出的规则) int1 := <-intChan // 注:也可以用『int1, ok := <-intChan』的方式从管道取出数据 int2 := <-intChan int3 := <-intChan fmt.Printf("int1、int2、int3 分别为 %d、%d、%d \n", int1, int2, int3) // int1、int2、int3 分别为 1、0、2 // 重要提醒①:用『int1, ok := <-intChan』的方式取数据,ok表示是否成功取出数据,ok和管道是否已经关闭没有关系。 // 就算管道已经关闭,但管道里仍有数据未被取出,那么就能成功取出数据,这种情况下ok的值就为true。 // 如果管道已经关闭,且已经没有数据,此时取出的就不是管道里的正常数据,而是零值,并且ok为false。 // 重要提醒②:建议统一使用『int1, ok := <-intChan』的方式从管道取出数据,以避免程序出现预期外的错误。 // 重要提醒③:无论使用哪种方式从管道取出数据,如果管道中没有数据都会发生阻塞,直到有数据写入管道或关闭管道。 // 查询管道的长度和容量(由于上面向管道发送了4个数据,又从管道取出了3个数据,还剩1个数据,故长度为1) fmt.Printf("intChan的长度为:%d,容量为:%d \n", len(intChan), cap(intChan)) // intChan的长度为:1,容量为:4 // 关闭管道(重要提醒:管道关闭后不能再向其发送数据,否则会报panic,但仍然可以尝试从管道取出数据) close(intChan) // 下面这行代码是不能执行的,因为上面已经关闭管道,再向管道发送数据就会报panic // intChan <- 9527 // panic: send on closed channel // 遍历管道 // 重要提醒①:遍历管道是遍历其当前已写入的数据,和管道容量没有关系。 // 重要提醒②:遍历管道其实是隐式从管道里取出数据,相当于“value := <-intChan”操作。 // 重要提醒③:如果管道已经没有数据并且一直不关闭,那么for-range就会一直阻塞。 // 重要提醒④:当管道被关闭,for-range循环会停止(相当于break效果),但如果管道里仍有数据,将会遍历完剩余数据再停止。 for value := range intChan { fmt.Printf("value = %d \n", value) // value = 4 } // for-range遍历管道将最后一个数据取出,所以长度为0 fmt.Printf("intChan的长度为:%d,容量为:%d \n", len(intChan), cap(intChan)) // intChan的长度为:0,容量为:4 // 虽然intChan管道已经关闭了,但仍可以尝试从管道取出数据(不会报错) num1 := <-intChan fmt.Printf("num1 = %d \n", num1) // num1 = 0 // 虽然intChan管道已经关闭了,但仍可以尝试从管道取出数据(不会报错) num2, ok := <-intChan if ok { fmt.Printf("成功:num2 = %d \n", num2) } else { fmt.Printf("失败:num2 = %d \n", num2) // 失败:num2 = 0 } } // ========== 总结 ========== // // 1、管道的本质就是一个队列,既然是队列那就有先进先出(FIFO,First In First Out)的特点,即先发送给管道的数据会被先取出。 // 2、管道在定义时需要指定数据类型(可以为int、string、map等各种数据类型)和容量,并且后续无法再修改数据类型和容量。 // 3、管道和切片一样,也有长度(len)和容量(cap)两个属性。当管道已经写满数据了还向管道写数据就会发生阻塞,直到有数据被取出; // 同样地,尝试从没有任何数据的管道取出数据也会发生阻塞,直到有数据被写入。 // 4、管道的发送数据和取出数据都是通过“<-”符号来操作,根据箭头指向可以很直观地看到是向管道写数据还是从管道取数据。 // 5、管道关闭后不能再向其发送数据,但仍然可以尝试从中取数据,且无论管道里是否有数据都不会报错,当最后一个数据被取出后, // 再从管道里取值就只能取得零值(注:零值指的是数字0、空字符串、false等值)。 // 6、for-range可遍历管道数据,这实际上是隐式从管道取出数据。当管道关闭就会中止循环,此时如果管道里仍有数据,将会遍历完剩余数据再停止。 // 7、管道是线程安全的,多个协程操作同一个管道不会发生资源竞争的问题,这也是管道的主要价值。 // 8、Go提供了函数关闭管道,但是没有提供函数检查管道是否关闭,这不是Go官方的疏忽,而是希望开发者遵循管道只能由数据发送方来关闭的原则, // 数据接收方只管接收,不需要理会管道是否关闭。
Copyright © 2024 码农人生. All Rights Reserved