github.com/zhongdalu/gf@v1.0.0/g/container/gqueue/gqueue.go (about)

     1  // Copyright 2017 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf.
     6  
     7  // Package gqueue provides a 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  	"github.com/zhongdalu/gf/g/container/glist"
    23  	"github.com/zhongdalu/gf/g/container/gtype"
    24  	"math"
    25  )
    26  
    27  type Queue struct {
    28  	limit  int              // Limit for queue size.
    29  	list   *glist.List      // Underlying list structure for data maintaining.
    30  	closed *gtype.Bool      // Whether queue is closed.
    31  	events chan struct{}    // Events for data writing.
    32  	C      chan interface{} // Underlying channel for data reading.
    33  }
    34  
    35  const (
    36  	// Size for queue buffer.
    37  	gDEFAULT_QUEUE_SIZE = 10000
    38  	// Max batch size per-fetching from list.
    39  	gDEFAULT_MAX_BATCH_SIZE = 10
    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()
    54  		q.events = make(chan struct{}, math.MaxInt32)
    55  		q.C = make(chan interface{}, gDEFAULT_QUEUE_SIZE)
    56  		go q.startAsyncLoop()
    57  	}
    58  	return q
    59  }
    60  
    61  // startAsyncLoop starts an asynchronous goroutine,
    62  // which handles the data synchronization from list <q.list> to channel <q.C>.
    63  func (q *Queue) startAsyncLoop() {
    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 > gDEFAULT_MAX_BATCH_SIZE {
    74  					length = gDEFAULT_MAX_BATCH_SIZE
    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) < gDEFAULT_QUEUE_SIZE {
   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 < gDEFAULT_MAX_BATCH_SIZE; i++ {
   126  		q.Pop()
   127  	}
   128  }
   129  
   130  // Len returns the length of the queue.
   131  func (q *Queue) Len() (length int) {
   132  	if q.list != nil {
   133  		length += q.list.Len()
   134  	}
   135  	length += len(q.C)
   136  	return
   137  }
   138  
   139  // Size is alias of Len.
   140  func (q *Queue) Size() int {
   141  	return q.Len()
   142  }