github.com/loov/combiner@v0.1.0/extcombiner/basic_spinning_uintptr.go (about) 1 package extcombiner 2 3 import ( 4 "sync/atomic" 5 "unsafe" 6 ) 7 8 // BasicSpinningUintptr is an unbounded spinning combiner queue using uintptr internally 9 // 10 // Based on https://software.intel.com/en-us/blogs/2013/02/22/combineraggregator-synchronization-primitive 11 type BasicSpinningUintptr struct { 12 head uintptr // *basicSpinningUintptrNode 13 _ [7]uint64 14 batcher Batcher 15 } 16 17 type basicSpinningUintptrNode struct { 18 next uintptr // *basicSpinningUintptrNode 19 argument interface{} 20 } 21 22 // NewBasicSpinningUintptr creates a BasicSpinningUintptr queue. 23 func NewBasicSpinningUintptr(batcher Batcher) *BasicSpinningUintptr { 24 return &BasicSpinningUintptr{ 25 batcher: batcher, 26 head: 0, 27 } 28 } 29 30 const basicSpinningUintptrLocked = uintptr(1) 31 32 // Do passes value to Batcher and waits for completion 33 func (c *BasicSpinningUintptr) Do(op interface{}) { 34 node := &basicSpinningUintptrNode{argument: op} 35 36 // c.head can be in 3 states: 37 // c.head == 0: no operations in-flight, initial state. 38 // c.head == LOCKED: single operation in-flight, no combining opportunities. 39 // c.head == pointer to some basicSpinningUintptrNode that is subject to combining. 40 // The args are chainded into a lock-free list via 'next' fields. 41 // node.next also can be in 3 states: 42 // node.next == pointer to other node. 43 // node.next == LOCKED: denotes the last node in the list. 44 // node.next == 0: the operation is finished. 45 46 // The function proceeds in 3 steps: 47 // 1. If c.head == nil, exchange it to LOCKED and become the combiner. 48 // Otherwise, enqueue own node into the c->head lock-free list. 49 50 var cmp uintptr 51 for { 52 cmp = atomic.LoadUintptr(&c.head) 53 xchg := basicSpinningUintptrLocked 54 if cmp != 0 { 55 // There is already a combiner, enqueue itself. 56 xchg = uintptr(unsafe.Pointer(node)) 57 node.next = cmp 58 } 59 60 if atomic.CompareAndSwapUintptr(&c.head, cmp, xchg) { 61 break 62 } 63 } 64 65 if cmp != 0 { 66 // 2. If we are not the combiner, wait for node.next to become nil 67 // (which means the operation is finished). 68 for try := 0; atomic.LoadUintptr(&node.next) != 0; spin(&try) { 69 } 70 } else { 71 // 3. We are the combiner. 72 73 // First, execute own operation. 74 c.batcher.Start() 75 defer c.batcher.Finish() 76 77 c.batcher.Do(node.argument) 78 79 // Then, look for combining opportunities. 80 for { 81 for { 82 cmp = atomic.LoadUintptr(&c.head) 83 // If there are some operations in the list, 84 // grab the list and replace with LOCKED. 85 // Otherwise, exchange to nil. 86 var xchg uintptr = 0 87 if cmp != basicSpinningUintptrLocked { 88 xchg = basicSpinningUintptrLocked 89 } 90 91 if atomic.CompareAndSwapUintptr(&c.head, cmp, xchg) { 92 break 93 } 94 } 95 96 // No more operations to combine, return. 97 if cmp == basicSpinningUintptrLocked { 98 break 99 } 100 101 // Execute the list of operations. 102 for cmp != basicSpinningUintptrLocked { 103 node = (*basicSpinningUintptrNode)(unsafe.Pointer(cmp)) 104 cmp = node.next 105 106 c.batcher.Do(node.argument) 107 // Mark completion. 108 atomic.StoreUintptr(&node.next, 0) 109 } 110 } 111 } 112 }