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