Golang亿点小细节之close()

@Golang亿点小细节之close()
你所忽略的,往往才是Bug的起源

close() 前置知识

1.不能去close()一个已经close()的channel

package main  func main(){ 	ch := make(chan int) 	close(ch) 	close(ch) 	// output: 	// panic: close of closed channel } 

2.channel被close()后,不可以写入(注意会panic:send on closed channel),但可以读取。读取规则是如果有缓存值则读缓存值,没有缓存值则读零。注意:v, ok := <-chok并不能去判断channel在何时关闭

package main  import "fmt"  func main() { 	ch := make(chan int,3) 	for i := 0; i < 3; i++ { 		ch <- i 	} 	close(ch) 	for i := 0; i < 5; i++ { 		v, ok := <-ch 		fmt.Println(v, ok) 	} 	fmt.Println("如果没有下一行就是写入失败被阻塞了啊") 	ch <- 1 	fmt.Println("我是下一行") 	/* 	output: 	0 true 	1 true 	2 true 	0 false 	0 false 	如果没有下一行就是写入失败被阻塞了啊 	panic: send on closed channel 	*/ } 

注意:v, ok := <-chok并不一定能去判断channel在何时关闭

package main  import "fmt"  func main() { 	ch := make(chan int, 3) 	for i := 1; i < 4; i++ { 		ch <- i 	} 	close(ch) 	for i := 0; i < 4; i++ { 		v, ok := <-ch 		fmt.Println(v,ok) 	} 	/* 	output 	1 true 	2 true  	3 true  	0 false 	*/ } 

为什么说使用ok模式去判断channel是否关闭是不可靠的呢?
如上输出所示,在通道内存在缓存值时,即使channel被关闭了,ok的值仍然是true,要记得0false才是一对哦!当channel被关闭且无缓存值时,ok才是false,同时使用channel时,最好在不需要使用的时候关闭,如果所有的goroutine都被阻塞了,会panic

上菜

下面的代码是脑爆Go语言群友昙花逐月提供,他提出问题,三个协程都有close(),为何没有输出
panic: close of closed channel,详情看注释哈

package main  import (    "fmt" )  func main() {    a := 0    fn := func() int {       a++       return a    }    ch := make(chan int, 1)    chh := make(chan int, 3)    for i := 0; i < 3; i++ {       go func(j int) {          for {             ch <- 1             n := fn()             if n > 100 {             	// 当第一个协程进入这段代码区域时,chh最终会被关闭             	// 剩下的两个协程因为chh被关闭,在写入按理来说应该会报panic: send on closed channel             	// 然而并未出现,原因在于其实协程在ch <- 1 的位置被阻塞了,为何?             	// ch容量为1,在第一个协程退出的时候并为将ch中的值取出,导致被阻塞                chh <- 1                close(chh)                return             }             fmt.Println("go", j, n)             <-ch          }       }(i)    }    for i := 0; i < 3; i++ {    	// 那这样是不是代表这chh中只有一个值,是的!但是为何主进程没阻塞呢?    	// 原因在于channle被关闭能读出0,所以这个循环在chh被关闭后马上就结束了       <-chh    } } 

饭后甜点

昙花逐月在解决上面的问题后,还提出一个问题,怎么利用channel来实现安全退出?,事实上上面的方案就是一个雏形,只是需要稍微修改一下代码,为了方便阅读,我在上述代码的基本盘不变的情况下重新组织一下代码。

func main() { 	data := make(chan int, 100) // 用来存储0-100 	code := make(chan int, 1)   // 相当于ch,写入code则有执行权 	count := make(chan int, 3)  // 相当于chh,用于统计  	for i := 0; i < 3; i++ { 		go func(code chan int, data <-chan int, count chan<- int) { 			for { 				code <- 1 // 获得执行权 				if v, ok := <-data; ok { 					fmt.Println(v) 					<-code //释放执行权 				} else { 					count <- 1 // 计数 					<- code //释放执行权 					return 				} 			} 		}(code, data, count) 	}  	go func(data chan<- int) { 		for i := 0; i < 101; i++ { 			data <- i 		} 		close(data) 	}(data)  	for i := 0; i < 3; { 		i = i + <-count // 统计 	} }  

事实上这种实现和

版权声明:玥玥 发表于 2021-03-20 8:06:20。
转载请注明:Golang亿点小细节之close() | 女黑客导航