您好我正在编写Lock
使用频道,该频道旨在锁定/解锁给定“应用”的操作。
一般的想法是,一个goroutine继续收听两个频道:lockCh
和unlockCh
。任何Lock()
操作都会向lockCh
发送一个自制频道,并等待从该自制频道进行阅读,通过阅读此频道表示Lock()
成功。
类似的处理适用于Unlock()
。
对于听众来说,它会在接受Lock()
时检查'app'是否已被锁定,如果是,它会将该自制频道放到服务员列表的尾部。如果有人Unlock()
,它会唤醒(通过向频道发送消息)下一个服务员或删除服务员列表,如果没有其他人在等待锁定。
代码放在下面,我不知道哪里出错了,但测试用例没有通过(它在几个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")
}
答案 0 :(得分:0)
您是否需要实现包含链表的状态机?使用Go的正常通道争用提供队列可能会有一个更简单的解决方案。
因此,客户端排队等待访问无缓冲的通道。锁定服务只是等待客户端。当一个人到达时,锁定服务进入锁定状态。使用无缓冲通道发出解锁信号,状态返回解锁状态。
虽然锁定,但锁定服务会忽略后续获取锁定的请求。那些客户端必须等待(Go运行时为此提供了一个队列)。每次解锁时,服务都可以接受下一个客户端。
for i := 0; i < loops; i++ {
l.Lock()
l.Unlock()
}
调用循环包含
l.Lock()
频道是无缓冲的非常重要,因为客户必须等到已完成的集合,才能知道他们已经获得(l.Unlock()
)或释放(If arVerb.Contains(txtBoxVerb.Text) Then ...
)锁定。
https://play.golang.org/p/2j4lkeOR_s
<强> PS 强>
我不禁想知道你是否只需要RWMutex(https://golang.org/pkg/sync/#RWMutex)
答案 1 :(得分:0)
我刚刚在代码中发现了错误。
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
}
最初,我认为head
中的tail
和receiveList
都是指针,所以我们总是可以在相同的服务员列表上操作,即使receiveList
不是&#39 ; ta指针类型。 (显然这是错的)
确实,如果我们只使用head
和tail
而不使用receiveList
的指针类型,那就没关系了。但是,我确实在lockRoutine中写入了它们,它正在写入它们的副本。这就是问题所在。
答案 2 :(得分:0)
对此有一个更简单的解决方案。单槽缓冲通道已经可以作为一个简单的锁定器。不需要管理器例程或链表列表:
// AppLock maintains a single exclusive lock for each app name string provided
type AppLock struct {
// Consider lock x:
// if map[x] doesn't exist, x is implicitly unlocked
// if map[x] exist and can be read from, x is unlocked
// if map[x] exist and blocks on read, x is locked
m map[string]chan struct{}
// RWMutex to protect the map writes, but allow free read access
mut sync.RWMutex
}
func NewAppLock() *AppLock {
l := new(AppLock)
l.m = make(map[string]chan struct{})
return l
}
func (l *AppLock) Lock(app string) {
l.mut.RLock()
ch, ok := l.m[app]
l.mut.RUnlock()
if ok {
<-ch // blocks until lock acquired
} else {
l.mut.Lock()
l.m[app] = make(chan struct{}, 1)
// we don't want to put anything on it yet, because we still want the lock
l.mut.Unlock()
}
}
func (l *AppLock) Unlock(app string) {
l.mut.RLock()
ch, ok := l.m[app]
l.mut.RUnlock()
if ok {
select {
case ch <- struct{}{}:
// do nothing, expected behavior
default:
// something already on channel, which means it wasn't locked
panic("Unlock called on app not previously locked")
}
} else {
panic("Unlock called on app not previously locked")
}
}
https://play.golang.org/p/o02BHfRto3
这为每个应用维护一个缓冲为1的通道。要锁定,您尝试从频道中读取。如果你收到一些东西(它只是代表锁的通用标记),你就得到了锁,然后你将令牌放回到通道上以解锁它。如果接收阻塞,则其他东西具有锁定,并且当其他例程解锁时接收将解除阻塞(将令牌放回到通道上)。
顺便提一下,这仍然可以作为先进先出系统,因为Go频道是有序的,并且将填补最早开始接收的接收(在那些仍在尝试接收的接收中)。
答案 3 :(得分:-1)
我认为问题在于您的main
for
循环。您没有关闭导致死锁的任何地方的c
频道。尝试将循环更改为:
for i := 0; i < 10; i++ {
var wg sync.WaitGroup // use WaitGroup
wg.Add(1)
go func(l *AppLock, loops int, cdone chan bool) {
defer wg.Done()
for i := 0; i < loops; i++ {
l.Lock(app)
l.Unlock(app)
}
cdone <- true
}(appLock, 1, c)
go func() {
wg.Wait()
close(c)
}()
}
工作代码:https://play.golang.org/p/cbXj0CPTGO
虽然这可以解决死锁问题,但这并不一定意味着程序的行为是正确的。你需要更好的测试来解决这个问题。