github.com/wangyougui/gf/v2@v2.6.5/container/gqueue/gqueue.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/wangyougui/gf.
     6  
     7  // Package gqueue provides dynamic/static concurrent-safe queue.
     8  //
     9  // Features:
    10  //
    11  // 1. FIFO queue(data -> list -> chan);
    12  //
    13  // 2. Fast creation and initialization;
    14  //
    15  // 3. Support dynamic queue size(unlimited queue size);
    16  //
    17  // 4. Blocking when reading data from queue;
    18  package gqueue
    19  
    20  import (
    21  	"math"
    22  
    23  	"github.com/wangyougui/gf/v2/container/glist"
    24  	"github.com/wangyougui/gf/v2/container/gtype"
    25  )
    26  
    27  // Queue is a concurrent-safe queue built on doubly linked list and channel.
    28  type Queue struct {
    29  	limit  int              // Limit for queue size.
    30  	list   *glist.List      // Underlying list structure for data maintaining.
    31  	closed *gtype.Bool      // Whether queue is closed.
    32  	events chan struct{}    // Events for data writing.
    33  	C      chan interface{} // Underlying channel for data reading.
    34  }
    35  
    36  const (
    37  	defaultQueueSize = 10000 // Size for queue buffer.
    38  	defaultBatchSize = 10    // Max batch size per-fetching from list.
    39  )
    40  
    41  // New returns an empty queue object.
    42  // Optional parameter `limit` is used to limit the size of the queue, which is unlimited in default.
    43  // When `limit` is given, the queue will be static and high performance which is comparable with stdlib channel.
    44  func New(limit ...int) *Queue {
    45  	q := &Queue{
    46  		closed: gtype.NewBool(),
    47  	}
    48  	if len(limit) > 0 && limit[0] > 0 {
    49  		q.limit = limit[0]
    50  		q.C = make(chan interface{}, limit[0])
    51  	} else {
    52  		q.list = glist.New(true)
    53  		q.events = make(chan struct{}, math.MaxInt32)
    54  		q.C = make(chan interface{}, defaultQueueSize)
    55  		go q.asyncLoopFromListToChannel()
    56  	}
    57  	return q
    58  }
    59  
    60  // Push pushes the data `v` into the queue.
    61  // Note that it would panic if Push is called after the queue is closed.
    62  func (q *Queue) Push(v interface{}) {
    63  	if q.limit > 0 {
    64  		q.C <- v
    65  	} else {
    66  		q.list.PushBack(v)
    67  		if len(q.events) < defaultQueueSize {
    68  			q.events <- struct{}{}
    69  		}
    70  	}
    71  }
    72  
    73  // Pop pops an item from the queue in FIFO way.
    74  // Note that it would return nil immediately if Pop is called after the queue is closed.
    75  func (q *Queue) Pop() interface{} {
    76  	return <-q.C
    77  }
    78  
    79  // Close closes the queue.
    80  // Notice: It would notify all goroutines return immediately,
    81  // which are being blocked reading using Pop method.
    82  func (q *Queue) Close() {
    83  	if !q.closed.Cas(false, true) {
    84  		return
    85  	}
    86  	if q.events != nil {
    87  		close(q.events)
    88  	}
    89  	if q.limit > 0 {
    90  		close(q.C)
    91  	} else {
    92  		for i := 0; i < defaultBatchSize; i++ {
    93  			q.Pop()
    94  		}
    95  	}
    96  }
    97  
    98  // Len returns the length of the queue.
    99  // Note that the result might not be accurate if using unlimited queue size as there's an
   100  // asynchronous channel reading the list constantly.
   101  func (q *Queue) Len() (length int64) {
   102  	bufferedSize := int64(len(q.C))
   103  	if q.limit > 0 {
   104  		return bufferedSize
   105  	}
   106  	return int64(q.list.Size()) + bufferedSize
   107  }
   108  
   109  // Size is alias of Len.
   110  // Deprecated: use Len instead.
   111  func (q *Queue) Size() int64 {
   112  	return q.Len()
   113  }
   114  
   115  // asyncLoopFromListToChannel starts an asynchronous goroutine,
   116  // which handles the data synchronization from list `q.list` to channel `q.C`.
   117  func (q *Queue) asyncLoopFromListToChannel() {
   118  	defer func() {
   119  		if q.closed.Val() {
   120  			_ = recover()
   121  		}
   122  	}()
   123  	for !q.closed.Val() {
   124  		<-q.events
   125  		for !q.closed.Val() {
   126  			if bufferLength := q.list.Len(); bufferLength > 0 {
   127  				// When q.C is closed, it will panic here, especially q.C is being blocked for writing.
   128  				// If any error occurs here, it will be caught by recover and be ignored.
   129  				for i := 0; i < bufferLength; i++ {
   130  					q.C <- q.list.PopFront()
   131  				}
   132  			} else {
   133  				break
   134  			}
   135  		}
   136  		// Clear q.events to remain just one event to do the next synchronization check.
   137  		for i := 0; i < len(q.events)-1; i++ {
   138  			<-q.events
   139  		}
   140  	}
   141  	// It should be here to close `q.C` if `q` is unlimited size.
   142  	// It's the sender's responsibility to close channel when it should be closed.
   143  	close(q.C)
   144  }