github.com/benz9527/toy-box/algo@v0.0.0-20240221120937-66c0c6bd5abd/queue/ring_buffer_cursor.go (about)

     1  package queue
     2  
     3  import (
     4  	"golang.org/x/sys/cpu"
     5  	"sync/atomic"
     6  	"unsafe"
     7  )
     8  
     9  // 2 ways for lock-free programming
    10  // Ref https://hedzr.com/golang/nolock/two-nolock-skills-in-go/
    11  // 1. structure entry mode: always copy the whole structure (independent element), for example golang slog.
    12  // 2. bumper loop mode: force to synchronize the whole process. Only works for low concurrency.
    13  //    We have to avoid slow handle process which will result in block next handle process,
    14  //    events heap up or event loss.
    15  //    For example, golang server/client conn.
    16  //    Bumper loop mode is appropriate for event distribution. Low concurrency means that
    17  //    the frequency if higher than 80ms per event.
    18  
    19  var (
    20  	_ RingBufferCursor = (*rbCursor)(nil)
    21  )
    22  
    23  const CacheLinePadSize = unsafe.Sizeof(cpu.CacheLinePad{})
    24  
    25  // rbCursor is a cursor for xRingBuffer.
    26  // Only increase, if it overflows, it will be reset to 0.
    27  // Occupy a whole cache line (flag+tag+data) and a cache line data is 64 bytes.
    28  // L1D cache: cat /sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size
    29  // L1I cache: cat /sys/devices/system/cpu/cpu0/cache/index1/coherency_line_size
    30  // L2 cache: cat /sys/devices/system/cpu/cpu0/cache/index2/coherency_line_size
    31  // L3 cache: cat /sys/devices/system/cpu/cpu0/cache/index3/coherency_line_size
    32  // MESI (Modified-Exclusive-Shared-Invalid)
    33  // RAM data -> L3 cache -> L2 cache -> L1 cache -> CPU register
    34  // CPU register (cache hit) -> L1 cache -> L2 cache -> L3 cache -> RAM data
    35  type rbCursor struct {
    36  	// sequence consistency data race free program
    37  	// avoid load into cpu cache will be broken by others data
    38  	// to compose a data race cache line
    39  	_      [CacheLinePadSize - unsafe.Sizeof(*new(uint64))]byte // padding for CPU cache line, avoid false sharing
    40  	cursor uint64                                               // space waste to exchange for performance
    41  	_      [CacheLinePadSize - unsafe.Sizeof(*new(uint64))]byte // padding for CPU cache line, avoid false sharing
    42  }
    43  
    44  func NewXRingBufferCursor() RingBufferCursor {
    45  	return &rbCursor{}
    46  }
    47  
    48  func (c *rbCursor) Next() uint64 {
    49  	// Golang atomic store with LOCK prefix, it means that
    50  	// it implements the Happens-Before relationship.
    51  	// But it is not clearly that atomic add satisfies the
    52  	// Happens-Before relationship.
    53  	// https://go.dev/ref/mem
    54  	return atomic.AddUint64(&c.cursor, 1)
    55  }
    56  
    57  func (c *rbCursor) NextN(n uint64) uint64 {
    58  	return atomic.AddUint64(&c.cursor, n)
    59  }
    60  
    61  func (c *rbCursor) Load() uint64 {
    62  	// Golang atomic load does not promise the Happens-Before
    63  	return atomic.LoadUint64(&c.cursor)
    64  }
    65  
    66  func (c *rbCursor) CAS(old, new uint64) bool {
    67  	return atomic.CompareAndSwapUint64(&c.cursor, old, new)
    68  }