multithreading - 为什么基于 channel 的 Lock block ?

标签 multithreading go locking channel

嗨,我正在写一个 Lock使用 channel ,旨在锁定/解锁给定“应用程序”的操作。

总体思路是,一个协程持续监听两个 channel :lockChunlockCh .任何Lock()操作发送自制 channel 到lockCh , 并等待从那个自制 channel 读取,从读取这个 channel 完成意味着 Lock()成功。

类似的过程适用于 Unlock() .

对于监听器 gorouting,它会在接受 Lock() 时检查“应用程序”是否已被锁定,如果是这样,它将把那个自制的 channel 放到等待列表的尾部。如果有人Unlock() ,它会唤醒(通过向 channel 发送消息)下一个服务员,或者如果没有其他人在等待锁,则删除服务员列表。

代码在下面,我不知道哪里错了,但是测试用例就是不通过(它在几个Lock()Unlock()之后阻塞!)

谢谢你给我一些建议。

type receiver struct {
            app  string
            ch   chan struct{}
            next *receiver
}

type receiveList struct {
            head *receiver
            tail *receiver
}

type AppLock struct {
            lockCh   chan receiver
            unlockCh chan receiver

            // Consider lock x:
            // if map[x] doesn't exist, x is unlocked
            // if map[x] exist but head is nil, x is locked but no waiter
            // if map[x] exist and head isn't nil, x is locked and there're waiters
            m map[string]receiveList
}

func NewAppLock() *AppLock {
            l := new(AppLock)
            l.lockCh = make(chan receiver)
            l.unlockCh = make(chan receiver)
            l.m = make(map[string]receiveList)

            go l.lockRoutine()
            return l
}

func (l *AppLock) Lock(app string) {
            ch := make(chan struct{})
            l.lockCh <- receiver{
                app: app,
                ch:  ch,
            }
            <-ch
}

func (l *AppLock) Unlock(app string) {
            ch := make(chan struct{})
            l.unlockCh <- receiver{
                app: app,
                ch:  ch,
            }
            <-ch
}

func (l *AppLock) lockRoutine() {
            for {
                select {
                case r := <-l.lockCh:
                    rlist, ok := l.m[r.app]
                    if ok { // already locked
                        if rlist.head == nil { // no waiter
                            rlist.head = &r
                            rlist.tail = &r
                        } else { // there're waiters, wait in queue
                            rlist.tail.next = &r
                            rlist.tail = &r
                        }
                    } else { // unlocked
                        rlist = receiveList{}
                        l.m[r.app] = rlist
                        r.ch <- struct{}{}
                    }
                case r := <-l.unlockCh:
                    rlist, ok := l.m[r.app]
                    if ok {
                        if rlist.head == nil { // no waiter
                            delete(l.m, r.app)
                            r.ch <- struct{}{}
                        } else { // there're waiters
                            candidate := rlist.head
                            if rlist.head == rlist.tail {
                                rlist.head = nil
                                rlist.tail = nil
                            } else {
                                rlist.head = rlist.head.next
                            }
                            candidate.ch <- struct{}{}
                            r.ch <- struct{}{}
                        }
                    } else {
                        panic("AppLock: inconsistent lock state")
                    }
                }
            }
}

测试:

func main() {
        app := "APP"
        appLock := NewAppLock()
        c := make(chan bool)

        for i := 0; i < 10; i++ {
            go func(l *AppLock, loops int, cdone chan bool) {
                for i := 0; i < loops; i++ {
                    l.Lock(app)
                    l.Unlock(app)
                }
                cdone <- true
            }(appLock, 1, c)
        }

        for i := 0; i < 10; i++ {
            <-c
        }

    fmt.Println("DONE")
}

最佳答案

我刚刚在我的代码中发现了错误。

type AppLock struct {
    lockCh   chan receiver
    unlockCh chan receiver

    // Consider lock x:
    // if map[x] doesn't exist, x is unlocked
    // if map[x] exist but head is nil, x is locked but no waiter
    // if map[x] exist and head isn't nil, x is locked and there're waiters
    m map[string]receiveList
}

最初,我认为 receiveList 中的 headtail 都是指针,所以我们总是可以对同一个 waiters-list 进行操作,即使receiveList 不是指针类型。 (显然是错的)

的确,如果我们只从headtail 读取数据,而不使用receiveList 的Pointer 类型也是可以的。但是,我确实在 lockRoutine 中写入它们,它正在写入它们的副本。这就是问题所在。

关于multithreading - 为什么基于 channel 的 Lock block ?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39332596/

相关文章:

sql-server - SQL Server,误导性的 XLOCK 和优化

c++ - 网络多线程

go - 在 go mod 下载期间获取错误未知修订依赖

android - 在 Activity 之间传递类的实例

golang cgo无法通过构建模式c-shared导出变量

go - 尝试实现死锁时使用零的无限循环

mysql - 如何优先处理某些查询?

mysql - 为什么IX锁与InnoDB中的另一个IX锁兼容?

java - Guava EventBus Google App Engine 友好吗?

java小程序: which threads invoke lifecycle hooks?