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

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