github.com/loov/combiner@v0.1.0/parking.go (about) 1 package combiner 2 3 import ( 4 "runtime" 5 "sync" 6 ) 7 8 // Parking is a bounded non-spinning combiner queue. 9 // 10 // This implementation is useful when the batcher work is large 11 // ore there are many goroutines concurrently calling Do. A good example 12 // would be a appending to a file. 13 type Parking struct { 14 limit int64 15 batcher Batcher 16 _ [5]int64 17 head nodeptr 18 _ [7]int64 19 lock sync.Mutex 20 cond sync.Cond 21 } 22 23 // NewParking creates a Parking combiner queue 24 func NewParking(batcher Batcher, limit int) *Parking { 25 q := &Parking{} 26 q.Init(batcher, limit) 27 return q 28 } 29 30 // Init initializes a Parking combiner. 31 // Note: NewParking does this automatically. 32 func (q *Parking) Init(batcher Batcher, limit int) { 33 if limit < 0 { 34 panic("combiner limit must be positive") 35 } 36 37 q.batcher = batcher 38 q.limit = int64(limit) 39 q.cond.L = &q.lock 40 } 41 42 // Do passes value to Batcher and waits for completion 43 //go:nosplit 44 //go:noinline 45 func (q *Parking) Do(arg interface{}) { 46 var mynode node 47 my := &mynode 48 my.argument = arg 49 defer runtime.KeepAlive(my) 50 51 var cmp nodeptr 52 for { 53 cmp = atomicLoadNodeptr(&q.head) 54 xchg := locked 55 if cmp != 0 { 56 xchg = my.ref() 57 my.next = cmp 58 } 59 if atomicCompareAndSwapNodeptr(&q.head, cmp, xchg) { 60 break 61 } 62 } 63 64 handoff := false 65 if cmp != 0 { 66 // busy wait 67 for i := 0; i < 8; i++ { 68 next := atomicLoadNodeptr(&my.next) 69 if next == 0 { 70 return 71 } 72 if next&handoffTag != 0 { 73 my.next &^= handoffTag 74 handoff = true 75 goto combining 76 } 77 } 78 79 q.lock.Lock() 80 for { 81 next := atomicLoadNodeptr(&my.next) 82 if next == 0 { 83 q.lock.Unlock() 84 return 85 } 86 if next&handoffTag != 0 { 87 my.next &^= handoffTag 88 handoff = true 89 q.lock.Unlock() 90 goto combining 91 } 92 93 q.cond.Wait() 94 } 95 } 96 97 combining: 98 q.batcher.Start() 99 q.batcher.Do(my.argument) 100 count := int64(1) 101 102 if handoff { 103 goto combine 104 } 105 106 combinecheck: 107 for { 108 cmp = atomicLoadNodeptr(&q.head) 109 var xchg uintptr = 0 110 if cmp != locked { 111 xchg = locked 112 } 113 114 if atomicCompareAndSwapNodeptr(&q.head, cmp, xchg) { 115 break 116 } 117 } 118 119 // No more operations to combine, return. 120 if cmp == locked { 121 q.batcher.Finish() 122 123 q.lock.Lock() 124 q.cond.Broadcast() 125 q.lock.Unlock() 126 return 127 } 128 129 combine: 130 // Execute the list of operations. 131 for cmp != locked { 132 other := nodeptrToNode(cmp) 133 if count == q.limit { 134 atomicStoreNodeptr(&other.next, other.next|handoffTag) 135 136 q.batcher.Finish() 137 138 q.lock.Lock() 139 q.cond.Broadcast() 140 q.lock.Unlock() 141 return 142 } 143 cmp = other.next 144 145 q.batcher.Do(other.argument) 146 count++ 147 // Mark completion. 148 atomicStoreNodeptr(&other.next, 0) 149 } 150 151 goto combinecheck 152 }