Mutex 实现可重入锁和线程安全的队列

52 阅读2分钟

Mutex 实现可重入锁和线程安全的队列

Mutex 不是可重入的锁。

方案一:goroutine id

简单方式,就是通过 runtime.Stack 方法获取栈帧信息,栈帧信息里包含 goroutine id。

goroutine 1 [running]:

main.main()
    ....../main.go:19 +0xb1

现在已经有很多成熟的方法了,可以支持多个 Go 版本的 goroutine id,给你推荐一个常用的库:petermattis/goid。

// RecursiveMutex 包装一个Mutex,实现可重入
type RecursiveMutex struct {
    sync.Mutex
    owner int64 // 当前持有锁的goroutine id
    recursion int32 // 这个goroutine 重入的次数
}

func (m *RecursiveMutex) Lock() {
    gid := goid.Get()
// 如果当前持有锁的goroutine就是这次调用的goroutine,说明是重入
    if atomic.LoadInt64(&m.owner) == gid {
        m.recursion++
       return
}
    m.Mutex.Lock()
// 获得锁的goroutine第一次调用,记录下它的goroutine id,调用次数加1
    atomic.StoreInt64(&m.owner, gid)
    m.recursion = 1
}

func (m *RecursiveMutex) Unlock() {
    gid := goid.Get()
    // 非持有锁的goroutine尝试释放锁,错误的使用
    if atomic.LoadInt64(&m.owner) != gid {
    panic(fmt.Sprintf("wrong the owner(%d): %d!", m.owner, gid))
}

    // 调用次数减1
    m.recursion--

    if m.recursion != 0 { // 如果这个goroutine还没有完全释放,则直接返回
       return
}
    // 此goroutine最后一次调用,需要释放锁
    atomic.StoreInt64(&m.owner, -1)
    m.Mutex.Unlock()
}

方案二:token

方案一是用 goroutine id 做 goroutine 的标识,我们也可以让 goroutine 自己来提供标识。不管怎么说,Go 开发者不期望你利用 goroutine id 做一些不确定的东西,所以,他们没有暴露获取 goroutine id 的方法。

调用者自己提供一个 token,获取锁的时候把这个 token 传 入,释放锁的时候也需要把这个 token 传入。通过用户传入的 token 替换方案一中goroutine id,其它逻辑和方案一一致。

欢迎大家把实现的代码评论一下,当然也有很多已经实现好的第三方库

线程安全的队列

type SliceQueue struct {
    data []interface{}
    mu sync.Mutex
}

func NewSliceQueue(n int) (q *SliceQueue) {
    return &SliceQueue{data: make([]interface{}, 0, n)}
}

// Enqueue 把值放在队尾
func (q *SliceQueue) Enqueue(v interface{}) {
    q.mu.Lock()
    q.data = append(q.data, v)
    q.mu.Unlock()
}

// Dequeue 移去队头并返回
func (q *SliceQueue) Dequeue() interface{} {
    q.mu.Lock()
    if len(q.data) == 0 {
    q.mu.Unlock()
    return nil
}
    v := q.data[0]
    q.data = q.data[1:]
    q.mu.Unlock()
    return v
}