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 // 统计
	}
}

事实上这种实现和sync.WaitGroup有异曲同工之妙

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 8
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值