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