如果不想自旋,可以把Lock、waitIsFinish和noticeIsFinish代码中的方式2注释掉,改用方式1。不过实际测试在低并发的情况下,自旋的执行效率更高,要根据实际业务场景选择使用哪种方式。
源代码如下:
import ( "runtime" "sync/atomic" ) const ( Gosched_Spin_Count = 100000 // 自旋多少次执行一次Gosched ) // 基于clh原理实现的锁,主要是为了保证按照获取锁顺序排队,使用自旋锁适合低并发场景 type CLHLock struct { tail atomic.Value // *CLHNode } type ICLHLock interface { Lock() (iclhNode, bool) Unlock(iclhNode) } var nilNode iclhNode = &clhNode{} func NewCLHLock() ICLHLock { lock := &CLHLock{} lock.tail.Store(nilNode) return lock } type clhNode struct { // isFinishCh chan struct{} isFinish bool } type iclhNode interface { waitIsFinish() noticeIsFinish() } func (lock *CLHLock) Lock() (iclhNode, bool) { newNode := &clhNode{ // isFinishCh: make(chan int, 1), isFinish: false, } hasPreNode := false // 将tail指向新节点 prevNode, _ := lock.tail.Swap(newNode).(iclhNode) // 如果存在前驱节点,就在前驱节点释放锁之前自旋等待 if prevNode != nilNode { hasPreNode = true // 等待前驱节点释放锁 prevNode.waitIsFinish() } return newNode, hasPreNode } func (lock *CLHLock) Unlock(node iclhNode) { if node == nil { return } // 尝试将tail设置为nil,表示当前goroutine释放了锁 lock.tail.CompareAndSwap(node, nilNode) // 如果设置tail为nil失败,表示有等待锁的后继节点, // 所以直接将当前节点的锁标志位设置为false并放弃修改tail。 node.noticeIsFinish() } func (node *clhNode) waitIsFinish() { // 方式1 ch // <-node.isFinishCh // 方式2 自旋 for i := 0; ; i++ { if node.isFinish { break } if i > Gosched_Spin_Count { runtime.Gosched() i = 0 } } } func (node *clhNode) noticeIsFinish() { // 方式1 ch // node.isFinishCh <- struct{}{} // close(node.isFinishCh) // 方式2 自旋 node.isFinish = true }
测试demo
var counter int var wg sync.WaitGroup func testLock(clh myutil.ICLHLock, i int) { defer wg.Done() for j := 0; j < 100000; j++ { lockNode, _ := clh.Lock() counter++ clh.Unlock(lockNode) } } func main() { // Adjust the number of goroutines for your desired concurrency level numGoroutines := 10 // Create an instance of your CLH lock clh := myutil.NewCLHLock() // Replace this with your CLH lock initialization // Test : Measure performance under high concurrency fmt.Println("Test : Performance under High Concurrency") fmt.Println("========================================") counter = 0 wg.Add(numGoroutines) startTime := time.Now() for i := 0; i < numGoroutines; i++ { go testLock(clh, i) } wg.Wait() endTime := time.Now() fmt.Printf("Counter value: %d\n", counter) fmt.Printf("Time taken for the test: %s\n", endTime.Sub(startTime)) fmt.Println("\nTesting completed.") }