github.com/egonelbre/exp@v0.0.0-20240430123955-ed1d3aa93911/combiner/tbbs.go (about) 1 package combiner 2 3 import ( 4 "sync" 5 "sync/atomic" 6 "unsafe" 7 ) 8 9 // based on https://software.intel.com/en-us/blogs/2013/02/22/combineraggregator-synchronization-primitive 10 type TBBSleepy struct { 11 head unsafe.Pointer // *tbbSleepyNode 12 _ [7]uint64 13 lock sync.Mutex 14 cond sync.Cond 15 // cacheline boundary 16 batcher Batcher 17 busy int64 18 } 19 20 type tbbSleepyNode struct { 21 next unsafe.Pointer // *tbbSleepyNode 22 argument interface{} 23 } 24 25 func NewTBBSleepy(batcher Batcher) *TBBSleepy { 26 c := &TBBSleepy{ 27 batcher: batcher, 28 head: nil, 29 } 30 c.cond.L = &c.lock 31 return c 32 } 33 34 func (c *TBBSleepy) DoAsync(op interface{}) { c.do(op, true) } 35 func (c *TBBSleepy) Do(op interface{}) { c.do(op, false) } 36 37 func (c *TBBSleepy) do(arg interface{}, async bool) { 38 node := &tbbSleepyNode{argument: arg} 39 40 var cmp unsafe.Pointer 41 for { 42 cmp = atomic.LoadPointer(&c.head) 43 node.next = cmp 44 if atomic.CompareAndSwapPointer(&c.head, cmp, unsafe.Pointer(node)) { 45 break 46 } 47 } 48 49 if cmp != nil { 50 if async { 51 return 52 } 53 54 // 2. If we are not the combiner, wait for arg.next to become nil 55 // (which means the operation is finished). 56 c.lock.Lock() 57 for { 58 if atomic.LoadPointer(&node.next) == nil { 59 c.lock.Unlock() 60 return 61 } 62 c.cond.Wait() 63 } 64 c.lock.Unlock() 65 } else { 66 // 3. We are the combiner. 67 68 // wait for previous combiner to finish 69 c.lock.Lock() 70 for { 71 if atomic.LoadInt64(&c.busy) != 1 { 72 break 73 } 74 c.cond.Wait() 75 } 76 atomic.StoreInt64(&c.busy, 1) 77 c.lock.Unlock() 78 79 // First, execute own operation. 80 c.batcher.Start() 81 82 // Grab the batch of operations only once 83 for { 84 cmp = atomic.LoadPointer(&c.head) 85 if atomic.CompareAndSwapPointer(&c.head, cmp, nil) { 86 break 87 } 88 } 89 90 node = (*tbbSleepyNode)(cmp) 91 // Execute the list of operations. 92 for node != nil { 93 next := (*tbbSleepyNode)(node.next) 94 c.batcher.Include(node.argument) 95 // Mark completion. 96 atomic.StorePointer(&node.next, nil) 97 node = next 98 } 99 100 c.batcher.Finish() 101 102 // allow next combiner to proceed 103 c.lock.Lock() 104 atomic.StoreInt64(&c.busy, 0) 105 c.cond.Broadcast() 106 c.lock.Unlock() 107 } 108 }