github.com/dubbogo/gost@v1.14.0/container/queue/poolqueue.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  //refs:https://github.com/golang/go/blob/2333c6299f340a5f76a73a4fec6db23ffa388e97/src/sync/poolqueue.go
    19  package gxqueue
    20  
    21  import (
    22  	"errors"
    23  	"sync/atomic"
    24  	"unsafe"
    25  )
    26  
    27  // SPMCLockFreeQ is a lock-free queue.
    28  type SPMCLockFreeQ interface {
    29  	PushHead(val interface{}) bool
    30  	PopHead() (interface{}, bool)
    31  	PopTail() (interface{}, bool)
    32  }
    33  
    34  // poolDequeue is a lock-free fixed-size single-producer,
    35  // multi-consumer queue. The single producer can both push and pop
    36  // from the head, and consumers can pop from the tail.
    37  //
    38  // It has the added feature that it nils out unused slots to avoid
    39  // unnecessary retention of objects. This is important for sync.Pool,
    40  // but not typically a property considered in the literature.
    41  type poolDequeue struct {
    42  	// headTail packs together a 32-bit head index and a 32-bit
    43  	// tail index. Both are indexes into vals modulo len(vals)-1.
    44  	//
    45  	// tail = index of oldest data in queue
    46  	// head = index of next slot to fill
    47  	//
    48  	// Slots in the range [tail, head) are owned by consumers.
    49  	// A consumer continues to own a slot outside this range until
    50  	// it nils the slot, at which point ownership passes to the
    51  	// producer.
    52  	//
    53  	// The head index is stored in the most-significant bits so
    54  	// that we can atomically add to it and the overflow is
    55  	// harmless.
    56  	headTail uint64
    57  
    58  	// vals is a ring buffer of interface{} values stored in this
    59  	// dequeue. The size of this must be a power of 2.
    60  	//
    61  	// vals[i].typ is nil if the slot is empty and non-nil
    62  	// otherwise. A slot is still in use until *both* the tail
    63  	// index has moved beyond it and typ has been set to nil. This
    64  	// is set to nil atomically by the consumer and read
    65  	// atomically by the producer.
    66  	vals []eface
    67  }
    68  
    69  type eface struct {
    70  	typ, val unsafe.Pointer
    71  }
    72  
    73  const dequeueBits = 32
    74  
    75  // dequeueLimit is the maximum size of a poolDequeue.
    76  //
    77  // This must be at most (1<<dequeueBits)/2 because detecting fullness
    78  // depends on wrapping around the ring buffer without wrapping around
    79  // the index. We divide by 4 so this fits in an int on 32-bit.
    80  const dequeueLimit = (1 << dequeueBits) / 4
    81  
    82  // dequeueNil is used in poolDeqeue to represent interface{}(nil).
    83  // Since we use nil to represent empty slots, we need a sentinel value
    84  // to represent nil.
    85  type dequeueNil *struct{}
    86  
    87  func (d *poolDequeue) unpack(ptrs uint64) (head, tail uint32) {
    88  	const mask = 1<<dequeueBits - 1
    89  	head = uint32((ptrs >> dequeueBits) & mask)
    90  	tail = uint32(ptrs & mask)
    91  	return
    92  }
    93  
    94  func (d *poolDequeue) pack(head, tail uint32) uint64 {
    95  	const mask = 1<<dequeueBits - 1
    96  	return (uint64(head) << dequeueBits) |
    97  		uint64(tail&mask)
    98  }
    99  
   100  // PushHead adds val at the head of the queue. It returns false if the
   101  // queue is full. It must only be called by a single producer.
   102  func (d *poolDequeue) PushHead(val interface{}) bool {
   103  	ptrs := atomic.LoadUint64(&d.headTail)
   104  	head, tail := d.unpack(ptrs)
   105  	if (tail+uint32(len(d.vals)))&(1<<dequeueBits-1) == head {
   106  		// Queue is full.
   107  		return false
   108  	}
   109  	slot := &d.vals[head&uint32(len(d.vals)-1)]
   110  
   111  	// Check if the head slot has been released by popTail.
   112  	typ := atomic.LoadPointer(&slot.typ)
   113  	if typ != nil {
   114  		// Another goroutine is still cleaning up the tail, so
   115  		// the queue is actually still full.
   116  		return false
   117  	}
   118  
   119  	// The head slot is free, so we own it.
   120  	if val == nil {
   121  		val = dequeueNil(nil)
   122  	}
   123  	*(*interface{})(unsafe.Pointer(slot)) = val
   124  
   125  	// Increment head. This passes ownership of slot to popTail
   126  	// and acts as a store barrier for writing the slot.
   127  	atomic.AddUint64(&d.headTail, 1<<dequeueBits)
   128  	return true
   129  }
   130  
   131  // PopHead removes and returns the element at the head of the queue.
   132  // It returns false if the queue is empty. It must only be called by a
   133  // single producer.
   134  func (d *poolDequeue) PopHead() (interface{}, bool) {
   135  	var slot *eface
   136  	for {
   137  		ptrs := atomic.LoadUint64(&d.headTail)
   138  		head, tail := d.unpack(ptrs)
   139  		if tail == head {
   140  			// Queue is empty.
   141  			return nil, false
   142  		}
   143  
   144  		// Confirm tail and decrement head. We do this before
   145  		// reading the value to take back ownership of this
   146  		// slot.
   147  		head--
   148  		ptrs2 := d.pack(head, tail)
   149  		if atomic.CompareAndSwapUint64(&d.headTail, ptrs, ptrs2) {
   150  			// We successfully took back slot.
   151  			slot = &d.vals[head&uint32(len(d.vals)-1)]
   152  			break
   153  		}
   154  	}
   155  
   156  	val := *(*interface{})(unsafe.Pointer(slot))
   157  	if val == dequeueNil(nil) {
   158  		val = nil
   159  	}
   160  	// Zero the slot. Unlike popTail, this isn't racing with
   161  	// pushHead, so we don't need to be careful here.
   162  	*slot = eface{}
   163  	return val, true
   164  }
   165  
   166  // PopTail removes and returns the element at the tail of the queue.
   167  // It returns false if the queue is empty. It may be called by any
   168  // number of consumers.
   169  func (d *poolDequeue) PopTail() (interface{}, bool) {
   170  	var slot *eface
   171  	for {
   172  		ptrs := atomic.LoadUint64(&d.headTail)
   173  		head, tail := d.unpack(ptrs)
   174  		if tail == head {
   175  			// Queue is empty.
   176  			return nil, false
   177  		}
   178  
   179  		// Confirm head and tail (for our speculative check
   180  		// above) and increment tail. If this succeeds, then
   181  		// we own the slot at tail.
   182  		ptrs2 := d.pack(head, tail+1)
   183  		if atomic.CompareAndSwapUint64(&d.headTail, ptrs, ptrs2) {
   184  			// Success.
   185  			slot = &d.vals[tail&uint32(len(d.vals)-1)]
   186  			break
   187  		}
   188  	}
   189  
   190  	// We now own slot.
   191  	val := *(*interface{})(unsafe.Pointer(slot))
   192  	if val == dequeueNil(nil) {
   193  		val = nil
   194  	}
   195  
   196  	// Tell pushHead that we're done with this slot. Zeroing the
   197  	// slot is also important so we don't leave behind references
   198  	// that could keep this object live longer than necessary.
   199  	//
   200  	// We write to val first and then publish that we're done with
   201  	// this slot by atomically writing to typ.
   202  	slot.val = nil
   203  	atomic.StorePointer(&slot.typ, nil)
   204  	// At this point pushHead owns the slot.
   205  
   206  	return val, true
   207  }
   208  
   209  // NewSPMCLockFreeQ new a SPMCLockFreeQ instance.
   210  func NewSPMCLockFreeQ(n int) (SPMCLockFreeQ, error) {
   211  	if n&(n-1) != 0 {
   212  		return nil, errors.New("the size of pool must be a power of 2")
   213  	}
   214  	d := &poolDequeue{
   215  		vals: make([]eface, n),
   216  	}
   217  	return d, nil
   218  }