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  }