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 }