github.com/loov/combiner@v0.1.0/extcombiner/basic_parking.go (about)

     1  package extcombiner
     2  
     3  import (
     4  	"runtime"
     5  	"sync"
     6  	"sync/atomic"
     7  	"unsafe"
     8  )
     9  
    10  // BasicParking is an unbounded non-spinning combiner queue
    11  //
    12  // Based on https://software.intel.com/en-us/blogs/2013/02/22/combineraggregator-synchronization-primitive
    13  type BasicParking struct {
    14  	head    unsafe.Pointer // *basicParkingNode
    15  	_       [7]uint64
    16  	lock    sync.Mutex
    17  	cond    sync.Cond
    18  	_       [0]uint64
    19  	batcher Batcher
    20  }
    21  
    22  type basicParkingNode struct {
    23  	argument interface{}
    24  	next     unsafe.Pointer // *basicParkingNode
    25  }
    26  
    27  // NewBasicParking creates a BasicParking queue.
    28  func NewBasicParking(batcher Batcher) *BasicParking {
    29  	c := &BasicParking{
    30  		batcher: batcher,
    31  		head:    nil,
    32  	}
    33  	c.cond.L = &c.lock
    34  	return c
    35  }
    36  
    37  var basicParkingLockedElem = basicParkingNode{}
    38  var basicParkingLockedNode = &basicParkingLockedElem
    39  var basicParkingLocked = (unsafe.Pointer)(basicParkingLockedNode)
    40  
    41  // DoAsync passes value to Batcher without waiting for completion
    42  func (c *BasicParking) DoAsync(op interface{}) { c.do(op, true) }
    43  
    44  // Do passes value to Batcher and waits for completion
    45  func (c *BasicParking) Do(op interface{}) { c.do(op, false) }
    46  
    47  func (c *BasicParking) do(op interface{}, async bool) {
    48  	node := &basicParkingNode{argument: op}
    49  
    50  	var cmp unsafe.Pointer
    51  	for {
    52  		cmp = atomic.LoadPointer(&c.head)
    53  		xchg := basicParkingLocked
    54  		if cmp != nil {
    55  			// There is already a combiner, enqueue itself.
    56  			xchg = (unsafe.Pointer)(node)
    57  			node.next = cmp
    58  		}
    59  
    60  		if atomic.CompareAndSwapPointer(&c.head, cmp, xchg) {
    61  			break
    62  		}
    63  	}
    64  
    65  	if cmp != nil {
    66  		if async {
    67  			return
    68  		}
    69  
    70  		for try := 0; try < busyspin; try++ {
    71  			if atomic.LoadPointer(&node.next) == nil {
    72  				return
    73  			}
    74  			runtime.Gosched()
    75  		}
    76  
    77  		c.lock.Lock()
    78  		for atomic.LoadPointer(&node.next) != nil {
    79  			c.cond.Wait()
    80  		}
    81  		c.lock.Unlock()
    82  	} else {
    83  		c.batcher.Start()
    84  		c.batcher.Do(node.argument)
    85  
    86  		for {
    87  			for {
    88  				cmp = atomic.LoadPointer(&c.head)
    89  				var xchg unsafe.Pointer
    90  				if cmp != basicParkingLocked {
    91  					xchg = basicParkingLocked
    92  				}
    93  
    94  				if atomic.CompareAndSwapPointer(&c.head, cmp, xchg) {
    95  					break
    96  				}
    97  			}
    98  
    99  			if cmp == basicParkingLocked {
   100  				break
   101  			}
   102  
   103  			for cmp != basicParkingLocked {
   104  				node = (*basicParkingNode)(unsafe.Pointer(cmp))
   105  				cmp = node.next
   106  
   107  				c.batcher.Do(node.argument)
   108  				atomic.StorePointer(&node.next, nil)
   109  			}
   110  		}
   111  		c.batcher.Finish()
   112  
   113  		c.lock.Lock()
   114  		c.cond.Broadcast()
   115  		c.lock.Unlock()
   116  	}
   117  }