chan的容量是固定的,在创建chan的时候被指定,此后就不可以再修改。在一些场景中需要容量可扩展的chan,对此收集了一些支持容量可扩展chan的开源项目:
- chanx:由InChannel、OutChannel和可扩展的ringbuffer组成。数据从InChannel写入,从OutChannel读取;并开启协程不断按照
InChannel -> Buffer -> OutChannel的方向搬运数据。 - infchan:使用
[]chan实现chan容量的可扩展。当chan已满,会创建一个新的chan放到[]chan中;当chan为空,会将空chan剔除出[]chan。 - unbounded-channel:使用并发安全的链表队列实现,支持容量可扩展;但是存在读取操作一直自旋、无阻塞的情况。
chanx
chanx直接将InChannel和OutChannel暴露给用户使用,其内部通过协程不断将数据按照InChannel -> Buffer -> OutChannel的路线搬运数据。
type T interface{}
type UnboundedChan struct {
// 进行了chan的方向封装,用户只能向In写入数据,从Out读取数据。
In chan<- T // channel for write
Out <-chan T // channel for read
// 循环Buffer用于存储多余数据,作为In和Out之间的缓冲,实现容量可扩展
// buffer只会被内部协程访问,因此没有做同步互斥处理
buffer *RingBuffer // buffer
}
func NewUnboundedChanSize(initInCapacity, initOutCapacity, initBufCapacity int) UnboundedChan {
in := make(chan T, initInCapacity)
out := make(chan T, initOutCapacity)
ch := UnboundedChan{In: in, Out: out, buffer: NewRingBuffer(initBufCapacity)}
// 启动协程,进行In->buffer->Out数据运输
go process(in, out, ch)
return ch
}
func process(in, out chan T, ch UnboundedChan) {
defer close(out)
loop:
for {
// 从In读取数据
val, ok := <-in
if !ok { // in is closed
break loop
}
// 优先向Out写入数据
select {
case out <- val:
continue
default:
}
// Out容量已满,向buffer写入数据
ch.buffer.Write(val)
for !ch.buffer.IsEmpty() {
select {
// 优先处理InChannel防止写操作阻塞, InChannel->buffer
case val, ok := <-in:
if !ok { // in is closed
break loop
}
ch.buffer.Write(val)
// Buffer->OutChannel; buffer清空时,进行buffer的缩容操作
case out <- ch.buffer.Peek():
ch.buffer.Pop()
if ch.buffer.IsEmpty() && ch.buffer.size > ch.buffer.initialSize { // after burst
ch.buffer.Reset()
}
}
}
}
// 当InChannel关闭时,将buffer数据放到OutChannel,直到都被消费完
for !ch.buffer.IsEmpty() {
out <- ch.buffer.Pop()
}
ch.buffer.Reset()
}
chanx将OutChannel和InChannel直接暴露给用户使用,支持InChannel的close操作。但是,尽量不要进行chanx OutChannel的close操作,容易引起panic。
infchan
infchan采用[]chan实现chan容量的可扩展性,当chan已满则申请新的chan放到[]chan里面。
type InfChan struct {
// mutex 控制并发
mut *sync.Mutex
// current指向正在被写入的chan
current chan interface{}
// chan切片,实现chan容量的可扩展性
chans []chan interface{}
}
// Insert 插入数据
func (ic *InfChan) Insert(i interface{}) {
ic.mut.Lock()
defer ic.mut.Unlock()
select {
case ic.current <- i:
default:
// chan已满,进行扩容操作
var ch = make(chan interface{}, internalChanSize)
ic.chans = append(ic.chans, ch)
ch <- i
ic.current = ch
}
}
// Remove 获取可读的chan
func (ic *InfChan) Remove() <-chan interface{} {
ic.mut.Lock()
defer ic.mut.Unlock()
if len(ic.chans[0]) == 0 {
if len(ic.chans) > 1 {
// chan已空,移除chan
ic.chans = append(ic.chans[1:])
}
}
return ic.chans[0]
}
unbounded-channel
unbounded-channel采用并发安全的队列实现,写入时支持容量的可扩展性;但是其读出时采用自旋等待的方式,直到队列中有新的数据才会结束等待,因此其读出操作不会被阻塞,这点与chan不符。
unbounded-channel的非堵塞队列借鉴了Simple, Fast, and Practical Non-Blocking and Blocking Concurrent Queue Algorithms论文
type node struct {
value interface{}
next *node
}
var noPointer = &node{value: nil, next: nil,}
func NewUnboundedChannel() *UnboundedChannel {
sentinel := &node{
value: nil,
next: noPointer,
}
return &UnboundedChannel{
head: sentinel,
tail: sentinel,
}
}
type UnboundedChannel struct {
head *node
tail *node
}
func (ch *UnboundedChannel) Enqueue(value interface{}) {
newNode := &node{
value: value,
next: noPointer,
}
for {
tail := ch.tail
next := tail.next
if tail == ch.tail {
if next == noPointer {
// 采用CAS操作,将tail的next指向newNode,即newNode的入队操作
nextPtr := (*unsafe.Pointer)(unsafe.Pointer(&ch.tail.next))
// 第39行
if atomic.CompareAndSwapPointer(nextPtr, unsafe.Pointer(next), unsafe.Pointer(newNode)) {
// 采用CAS操作,将tail指向newNode,即入队成功、更新tail
tailPtr := (*unsafe.Pointer)(unsafe.Pointer(&ch.tail))
// 第42行
atomic.CompareAndSwapPointer(tailPtr, unsafe.Pointer(tail), unsafe.Pointer(newNode))
break
}
} else {
// 当并发入队时,其他协程执行了newNode入队(第39行代码),这时tail不是真实的尾部
// 采用CAS操作,将tail指向其nextNode
tailPtr := (*unsafe.Pointer)(unsafe.Pointer(&ch.tail))
atomic.CompareAndSwapPointer(tailPtr, unsafe.Pointer(tail), unsafe.Pointer(next))
}
}
}
}
func (ch *UnboundedChannel) Dequeue() interface{} {
for {
head := ch.head
tail := ch.tail
// head为sentinel节点,所以第一个value位于head.next
firstElement := head.next
if head == ch.head {
if head == tail {
// 队列为空,自旋等待
if firstElement == noPointer {
continue
}
// 其他协程执行了newNode入队(第39行代码),但是tail更新失败(第42行代码) -> 需要更新尾指针
tailPtr := (*unsafe.Pointer)(unsafe.Pointer(&ch.tail))
atomic.CompareAndSwapPointer(tailPtr, unsafe.Pointer(tail), unsafe.Pointer(firstElement))
} else {
// 将head元素出队,并更新head指针
value := firstElement.value
headPtr := (*unsafe.Pointer)(unsafe.Pointer(&ch.head))
if atomic.CompareAndSwapPointer(headPtr, unsafe.Pointer(head), unsafe.Pointer(firstElement)) {
return value
}
}
}
}
}