返回顶部

golang简单实现CLHLock,不可重入的clh自旋锁

如果不想自旋,可以把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.")
}

 

 

posted @ 2023-08-14 10:49  雨山木风  阅读(48)  评论(0编辑  收藏  举报