前言:
Go channel是Go语言中用于协程间通信的一种特殊类型。通道(channel)是Go语言并发编程的两大基石之一,用于goroutine之间的同步和通信。 通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。
通道有以下几个特点:
1.通道是引用类型,必须使用make函数创建,其容量一旦确定,不会动态增加。
2.当写满时,不可以写;取空时,不可以取。
3.发送操作将持续阻塞直到数据被接收,而接收操作也将持续阻塞直到有数据可接收。
4.通道一次只能接收一个数据元素。
5.通道提供了一种无需共享内存即可进行通信的方式,通过通道进行通信可以避免并发安全问题。
今天的文章我们就channel相关问题展开。
close channel 设计理念
func closechan(c *hchan) { // 堆代码 duidaima.com // 关闭一个 nil channel, 抛出 panic if c == nil { panic(plainError("close of nil channel")) } }为什么关闭一个已经关闭的 channel 会 panic ?官方这样设计的初衷,应该是希望开发者不要依赖于 close 函数,而是要求开发者通过合理设计 goroutine + channel 工作流来提高程序的健壮性。
package main import ( "fmt" "time" ) func main() { ch := make(chan int) go func() { if val, ok := <-ch; !ok { // channel 已关闭 fmt.Println("channel closed") } else { fmt.Printf("val = %d", val) } }() ch <- 1024 close(ch) time.Sleep(time.Second) }如何实现健壮的 channel close 方法
package main // 堆代码 duidaima.com import ( "fmt" ) func main() { defer func() { if err := recover(); err != nil { fmt.Printf("recover err: %v\n", err) } }() ch := make(chan int) close(ch) // 关闭一个已经关闭的 channel close(ch) }2. sync.Once
package main import ( "sync" ) type myChan struct { ch chan int once sync.Once } func (c *myChan) close() { c.once.Do(func() { close(c.ch) }) } func main() { ch := &myChan{ ch: make(chan int), } ch.close() // 关闭一个已经关闭的 channel ch.close() }3. atomic.CAS
package main import "sync/atomic" type myChan struct { ch chan int closed int32 } func (c *myChan) close() { if atomic.CompareAndSwapInt32(&c.closed, 0, 1) { close(c.ch) } } func main() { ch := &myChan{ ch: make(chan int), } ch.close() // 关闭一个已经关闭的 channel ch.close() }4. context.Context
package main import ( "context" ) type myChan struct { ch chan int ctx context.Context cancel context.CancelFunc } func (c *myChan) close() { select { case <-c.ctx.Done(): return default: close(c.ch) // 事件同步 c.cancel() } } func main() { ctx, cancel := context.WithCancel(context.Background()) ch := &myChan{ ch: make(chan int), ctx: ctx, cancel: cancel, } ch.close() // 关闭一个已经关闭的 channel ch.close() }
8.缓冲 channel 虽然避免了阻塞,但是有潜在的数据竞态,而且需要考虑缓冲区大小,设计不合理容易浪费资源