github.com/sandwich-go/boost@v1.3.29/xchan/unbounded_chan.go (about)

     1  package xchan
     2  
     3  import (
     4  	"context"
     5  	"sync/atomic"
     6  )
     7  
     8  // UnboundedChan is an unbounded chan.
     9  // In is used to write without blocking, which supports multiple writers.
    10  // and Out is used to read, which supports multiple readers.
    11  // You can close the in channel if you want.
    12  type UnboundedChan[T any] struct {
    13  	bufCount int64
    14  	In       chan<- T       // channel for write
    15  	Out      <-chan T       // channel for read
    16  	buffer   *RingBuffer[T] // buffer
    17  	cc       *Options
    18  }
    19  
    20  // Len 所有待读取的数据的长度
    21  func (c UnboundedChan[T]) Len() int {
    22  	return len(c.In) + c.BufLen() + len(c.Out)
    23  }
    24  
    25  // BufLen 获取缓存中的数据的长度,不包含外发Out channel中数据的长度
    26  func (c UnboundedChan[T]) BufLen() int {
    27  	return int(atomic.LoadInt64(&c.bufCount))
    28  }
    29  
    30  // NewUnboundedChan creates the unbounded chan.
    31  // in is used to write without blocking, which supports multiple writers.
    32  // and out is used to read, which supports multiple readers.
    33  // You can close the in channel if you want.
    34  func NewUnboundedChan[T any](ctx context.Context, initCapacity int, opts ...Option) *UnboundedChan[T] {
    35  	return NewUnboundedChanSize[T](ctx, initCapacity, initCapacity, initCapacity, opts...)
    36  }
    37  
    38  // NewUnboundedChanSize is like NewUnboundedChan but you can set initial capacity for In, Out, Buffer.
    39  func NewUnboundedChanSize[T any](ctx context.Context, initInCapacity, initOutCapacity, initBufCapacity int, opts ...Option) *UnboundedChan[T] {
    40  	in := make(chan T, initInCapacity)
    41  	out := make(chan T, initOutCapacity)
    42  	ch := UnboundedChan[T]{In: in, Out: out, buffer: NewRingBuffer[T](initBufCapacity), cc: NewOptions(opts...)}
    43  
    44  	go process(ctx, in, out, &ch)
    45  
    46  	return &ch
    47  }
    48  
    49  func process[T any](ctx context.Context, in, out chan T, ch *UnboundedChan[T]) {
    50  	defer close(out)
    51  	drain := func() {
    52  		for !ch.buffer.IsEmpty() {
    53  			select {
    54  			case out <- ch.buffer.Pop():
    55  				atomic.AddInt64(&ch.bufCount, -1)
    56  			case <-ctx.Done():
    57  				return
    58  			}
    59  		}
    60  
    61  		ch.buffer.Reset()
    62  		atomic.StoreInt64(&ch.bufCount, 0)
    63  	}
    64  	for {
    65  		select {
    66  		case <-ctx.Done():
    67  			return
    68  		case val, ok := <-in:
    69  			if !ok { // in is closed
    70  				drain()
    71  				return
    72  			}
    73  
    74  			// make sure values' order
    75  			// buffer has some values
    76  			if atomic.LoadInt64(&ch.bufCount) > 0 {
    77  				ch.buffer.Write(val)
    78  				newVal := atomic.AddInt64(&ch.bufCount, 1)
    79  				if ch.cc.CallbackOnBufCount != 0 && newVal > ch.cc.CallbackOnBufCount {
    80  					ch.cc.Callback(newVal)
    81  				}
    82  			} else {
    83  				// out is not full
    84  				select {
    85  				case out <- val:
    86  					//放入成功,说明out刚才还没有满,buffer中也没有额外的数据待处理,所以回到loop开始
    87  					continue
    88  				default:
    89  				}
    90  
    91  				// out is full
    92  				ch.buffer.Write(val)
    93  				newVal := atomic.AddInt64(&ch.bufCount, 1)
    94  				if ch.cc.CallbackOnBufCount != 0 && newVal > ch.cc.CallbackOnBufCount {
    95  					ch.cc.Callback(newVal)
    96  				}
    97  			}
    98  
    99  			for !ch.buffer.IsEmpty() {
   100  				select {
   101  				case <-ctx.Done():
   102  					return
   103  				case val, ok := <-in:
   104  					if !ok { // in is closed
   105  						drain()
   106  						return
   107  					}
   108  					ch.buffer.Write(val)
   109  					newVal := atomic.AddInt64(&ch.bufCount, 1)
   110  					if ch.cc.CallbackOnBufCount != 0 && newVal > ch.cc.CallbackOnBufCount {
   111  						ch.cc.Callback(newVal)
   112  					}
   113  				case out <- ch.buffer.Peek():
   114  					ch.buffer.Pop()
   115  					atomic.AddInt64(&ch.bufCount, -1)
   116  					if ch.buffer.IsEmpty() && ch.buffer.size > ch.buffer.initialSize { // after burst
   117  						ch.buffer.Reset()
   118  						atomic.StoreInt64(&ch.bufCount, 0)
   119  					}
   120  				}
   121  			}
   122  		}
   123  	}
   124  }