Golang中Mutex的使用 - 为什么代码会锁住
Golang中Mutex的使用 - 为什么代码会锁住 运行下面的代码会产生死锁。正如解释的那样:
因为 get() 方法会获取锁,然后调用 count() 方法,而 count() 方法在 set() 方法解锁之前也会获取锁
实际上不太清楚的是,为什么一个方法在对象上获取锁,然后这个方法调用另一个也在同一对象上获取锁的方法会导致死锁。这是什么机制?而且 count() 方法在 set() 方法解锁之前也会获取锁 有什么问题?
即使 count() 方法也会获取锁,它也会在 set() 完成之前释放锁。那么为什么这两个方法不能处理这个问题呢?
package main
import (
"fmt"
"sync"
)
type DataStore struct {
sync.Mutex
cache map[string]string
}
func New() *DataStore {
return &DataStore{
cache: make(map[string]string),
}
}
func (ds *DataStore) set(key string, value string) {
ds.Lock()
defer ds.Unlock()
ds.cache[key] = value
}
func (ds *DataStore) get(key string) string {
ds.Lock()
defer ds.Unlock()
if ds.count() > 0 {
item := ds.cache[key]
return item
}
return ""
}
func (ds *DataStore) count() int {
ds.Lock()
defer ds.Unlock()
return len(ds.cache)
}
func main() {
store := New()
store.set("Go", "Lang")
result := store.get("Go")
fmt.Println(result)
}
更多关于Golang中Mutex的使用 - 为什么代码会锁住的实战教程也可以访问 https://www.itying.com/category-94-b0.html
GreyShatter:
Go不支持递归互斥锁。
所以正是因为使用了不被支持的递归互斥锁才导致了死锁。
在你这种情况下,如果只有一个互斥锁,我认为在从其他方法访问资源(映射)之前必须先解锁。我认为在同一互斥锁上使用嵌套锁不是一个好主意。
不要过度设计:
将 get() 函数内部的 ds.count() 调用替换为 len(ds.cache) 以避免破坏锁。
或者直接删除整个 if ds.count() > 0 代码块,因为你不需要先检查长度。
直接返回 ds.cache[key] 即可。
kync:
您在这种情况下不需要使用递归互斥锁,sync.RWMutex 将是解决此问题的方案。
RWMutex 在这里并不是解决方案。根据文档:
它不应用于递归读锁定;被阻塞的 Lock 调用会阻止新的读取器获取锁。请参阅 RWMutex 类型的文档。
编辑: 哦,不对。是我看错了。是的,您在这种情况下是对的,抱歉。
嗯,确实如此,但我的问题角度略有不同。
据我理解,第二个方法 count() 无法访问 ds 数据存储,因为它已被 get 方法中使用的互斥锁锁定。这就是为什么作者在说以下这段话时意图不明确的原因:
count() 方法将在 set() 方法解锁之前再获取一个锁
如果 ds 已经被锁定,count() 方法是否还能再获取一个锁?
func main() {
fmt.Println("hello world")
}
在你的场景中不需要使用递归互斥锁,sync.RWMutex 可以解决这个问题。读取时使用 RLock,写入时使用 Lock。
func (ds *DataStore) set(key string, value string) {
ds.Lock()
defer ds.Unlock()
ds.cache[key] = value
}
func (ds *DataStore) get(key string) string {
ds.RLock()
defer ds.RUnlock()
if ds.count() > 0 {
item := ds.cache[key]
return item
}
return ""
}
func (ds *DataStore) count() int {
ds.RLock()
defer ds.RUnlock()
return len(ds.cache)
}
Go 不支持递归互斥锁。而且有观点认为根本不应该使用它们:
递归锁定在 Go 中的实现
标签:go
但如果你确实需要它们,可以使用专门的库,例如这个:
90TechSAS/go-recursive-mutex
recmutex 是一个用于处理递归互斥锁的微型互斥锁库 - 90TechSAS/go-recursive-mutex
我同意递归的读写锁通常会导致问题。现在我已经退休了,正在实现一个干净版本的UniVerse,这是一个Pick模型数据库。在某种连接形式中,Pick TFile和Prime Information的TRANS()结构可以从主键读取表行(和列)。由于行存储在哈希表中,哈希桶上持有读写锁。因此,您需要嵌套读写锁,以便在次要读取终止时仍保持锁定。我的解决方案是跟踪请求(按表和哈希桶),并在计数降至零时释放读写锁。
我当前的问题是每个用户都是一个独立的goroutine。如果某些用户代码创建了无限循环,如何终止该goroutine,或使其跳出"某个循环"。如果它正在"循环",它不会通过select循环来查看上下文取消。
可重入互斥锁
在计算机科学中,可重入互斥锁(递归互斥锁、递归锁)是一种特殊类型的互斥设备,可以被同一进程/线程多次锁定而不会导致死锁。
当互斥锁已被锁定时,对普通互斥锁执行"锁定"操作的任何尝试都会失败或阻塞;而在递归互斥锁上,当且仅当加锁线程是已经持有该锁的线程时,此操作才会成功。通常,递归…
也许这能帮上忙 🙂
对我来说,这看起来像是数据库事务。也许我理解错了
在Go语言中,sync.Mutex 是不可重入锁(non-reentrant mutex)。这意味着同一个goroutine不能多次获取同一个互斥锁而不释放它。在你的代码中,get 方法获取锁后,在锁仍然持有的情况下调用了 count 方法,而 count 方法也尝试获取同一个锁,这就导致了死锁。
具体机制如下:
get方法调用ds.Lock()获取锁- 在锁仍然持有的情况下,
get方法调用count方法 count方法尝试调用ds.Lock()再次获取同一个锁- 由于锁已经被当前goroutine持有,
count方法会一直阻塞等待锁释放 - 但
get方法只有在count方法返回后才能释放锁 - 这就形成了循环等待,导致死锁
示例代码展示问题所在:
package main
import (
"fmt"
"sync"
)
type DataStore struct {
sync.Mutex
cache map[string]string
}
func New() *DataStore {
return &DataStore{
cache: make(map[string]string),
}
}
func (ds *DataStore) set(key string, value string) {
ds.Lock()
defer ds.Unlock()
ds.cache[key] = value
}
// 有问题的get方法 - 会导致死锁
func (ds *DataStore) get(key string) string {
ds.Lock()
defer ds.Unlock()
// 问题:在锁持有的情况下调用另一个需要锁的方法
if ds.count() > 0 { // 这里会死锁
item := ds.cache[key]
return item
}
return ""
}
func (ds *DataStore) count() int {
ds.Lock() // 这里会阻塞,因为锁已经被get方法持有
defer ds.Unlock()
return len(ds.cache)
}
// 修复后的get方法
func (ds *DataStore) getFixed(key string) string {
ds.Lock()
defer ds.Unlock()
// 直接访问缓存,不调用需要锁的方法
if len(ds.cache) > 0 {
item := ds.cache[key]
return item
}
return ""
}
// 或者将count方法改为不需要锁的版本
func (ds *DataStore) countFixed() int {
// 不获取锁,由调用方保证线程安全
return len(ds.cache)
}
func main() {
store := New()
store.set("Go", "Lang")
// 使用修复后的方法
result := store.getFixed("Go")
fmt.Println(result)
// 或者这样使用count方法(在锁保护下)
store.Lock()
count := store.countFixed()
store.Unlock()
fmt.Println("Count:", count)
}
修复方案:
- 在持有锁的方法中不要调用其他需要相同锁的方法
- 将不需要锁保护的逻辑提取到独立的方法中
- 使用
sync.RWMutex来区分读锁和写锁,允许多个读操作并发执行
使用 sync.RWMutex 的改进版本:
package main
import (
"fmt"
"sync"
)
type DataStore struct {
sync.RWMutex
cache map[string]string
}
func New() *DataStore {
return &DataStore{
cache: make(map[string]string),
}
}
func (ds *DataStore) set(key string, value string) {
ds.Lock()
defer ds.Unlock()
ds.cache[key] = value
}
func (ds *DataStore) get(key string) string {
ds.RLock()
defer ds.RUnlock()
if ds.count() > 0 {
item := ds.cache[key]
return item
}
return ""
}
func (ds *DataStore) count() int {
ds.RLock() // 使用读锁,可以多个goroutine同时获取
defer ds.RUnlock()
return len(ds.cache)
}
func main() {
store := New()
store.set("Go", "Lang")
result := store.get("Go")
fmt.Println(result)
}


