gopkg的chan的实现初探

75 阅读2分钟

概述

Go channel 是支持面向消息编程的基础,作为内置的语法特性,深受消息爱好者的喜爱之外,容易被初学者滥用,而错误使用chan的后果重则程序崩溃,轻则导致问题难以排查,开源的gopkg尝试提供一种新的接口代替chan,从功能上来说确实增强了鲁棒性,但是性能和使用流畅度恐怕不及Gopher预期。

details

Channel

Channel 模拟出了一个原生chan的基本方法,这些方法杜绝了几种panic或者危险可能:

  • 重复关闭chan panic
  • 写入已关闭chan panic
  • 生产大于消费,同步消费则导致生产端阻塞,request超时进入恶性循环,异步消费则会有OOM风险,整个程序异常
  • 无人消费,或者channel未被及时消费,会导致生产端直接阻塞卡死,而Go在语言层面难以及时报告这种错误
type Channel interface {
	Input(v interface{})
	Output() <-chan interface{}
	Len() int
	Stats() (produced uint64, consumed uint64)
	Close()
}

channel struct

type channel struct {
	size             int
	state            int32
	consumer         chan interface{}
	nonblock         bool // non blocking mode
	timeout          time.Duration
	timeoutCallback  func(interface{})
	producerThrottle Throttle
	consumerThrottle Throttle
	throttleWindow   time.Duration
	// statistics
	produced uint64 // item already been insert into buffer
	consumed uint64 // item already been sent into Output chan
	// buffer
	buffer     *list.List // TODO: use high perf queue to reduce GC here
	bufferCond *sync.Cond
	bufferLock sync.Mutex
}

Input

  • 封装v,增加timeout,尝试写入buffer(enqueueBuffer)
  • 如果是阻塞模式则会cond.Wait在buffer有空闲

Output

直接返回一个原生chan,由于input时不是直接写入chan,因此这个output chan是具有流量控制能力的,lag都在buffer内解决

func (c *channel) Output() <-chan interface{} {
	return c.consumer
}

Len

由2个原子变量计算出真实Len

func (c *channel) Len() int {
	produced, consumed := c.Stats()
	l := produced - consumed
	return int(l)
}

Close

func (c *channel) Close() {
	if !atomic.CompareAndSwapInt32(&c.state, 0, -1) {
		return
	}
	// stop consumer
	c.bufferLock.Lock()
	c.buffer.Init() // clear buffer
	c.bufferLock.Unlock()
	c.bufferCond.Broadcast()
}

innner ref