|
@@ -9,7 +9,7 @@ import (
|
|
|
|
|
|
|
|
// goroutineID 返回当前 goroutine 的 ID。
|
|
// goroutineID 返回当前 goroutine 的 ID。
|
|
|
//
|
|
//
|
|
|
-// 仅用于实现下面的"操作级可重入锁":同一 goroutine 在一个写事务内多次进入公开
|
|
|
|
|
|
|
+// 仅用于实现下面的"操作级可重入读写锁":同一 goroutine 在一个写事务内多次进入公开
|
|
|
// 操作 (BeginTransaction → InsertRow → ... → Commit) 时重入不卡死,跨 goroutine
|
|
// 操作 (BeginTransaction → InsertRow → ... → Commit) 时重入不卡死,跨 goroutine
|
|
|
// 则互斥排队。Go 官方不鼓励在业务里依赖 goroutine ID,但用它实现可重入互斥是
|
|
// 则互斥排队。Go 官方不鼓励在业务里依赖 goroutine ID,但用它实现可重入互斥是
|
|
|
// 少数正当用途之一。
|
|
// 少数正当用途之一。
|
|
@@ -23,41 +23,69 @@ func goroutineID() int64 {
|
|
|
return v
|
|
return v
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// accessLock 操作级可重入串行锁。
|
|
|
|
|
|
|
+// accessLock 操作级可重入读写锁。
|
|
|
//
|
|
//
|
|
|
-// 设计定位:引擎是"单写者、操作串行"模型,不支持并发读写。这把锁把多 goroutine
|
|
|
|
|
-// 的误并发调用自动串行化 —— 调用方即使不自己加锁,也不会出现:
|
|
|
|
|
|
|
+// 设计定位:引擎默认是"单写者、操作串行"模型,但允许并发读。这把锁把多 goroutine
|
|
|
|
|
+// 的并发调用自动安全化 —— 调用方即使不自己加锁,也不会出现:
|
|
|
// - 两个写操作并发,把页 / 事务结构改坏;
|
|
// - 两个写操作并发,把页 / 事务结构改坏;
|
|
|
-// - 读者在写操作改到一半时读到"新叶子 + 旧父节点"这类中间态(脏读)。
|
|
|
|
|
|
|
+// - 读者在写操作改到一半时读到中间态(脏读);
|
|
|
|
|
+// - 多个查询互相阻塞(读读并行)。
|
|
|
//
|
|
//
|
|
|
-// 同一 goroutine 重入(典型:显式事务 Begin 持锁后,事务内的 InsertRow / FindByPK
|
|
|
|
|
-// 等子操作再次进入)只加深 depth、不阻塞;跨 goroutine 进入则在 cond 上排队,
|
|
|
|
|
-// 直到持有者在 Commit / Rollback 释放。
|
|
|
|
|
|
|
+// 锁语义:
|
|
|
//
|
|
//
|
|
|
-// 内部 mu 只用于短暂保护 owner/depth 字段与 cond 等待,绝不跨整个临界区持有 —
|
|
|
|
|
-// 否则同 goroutine 重入时会在不可重入的 sync.Mutex 上自死锁。
|
|
|
|
|
|
|
+// RLock / RUnlock —— 读锁, 可并发, 不阻塞其他 RLock, 被写锁阻塞
|
|
|
|
|
+// Lock / Unlock —— 写锁, 排他, 阻塞所有 RLock 和 Lock
|
|
|
|
|
+//
|
|
|
|
|
+// 可重入规则 (关键):
|
|
|
|
|
+//
|
|
|
|
|
+// 同 goroutine 已持有写锁 → 再 Lock 加深 depth, 再 RLock 直接跳过
|
|
|
|
|
+// 同 goroutine 已持有自己的读锁 → 再 RLock 加深 rDepth[g], 再 Lock 直接跳过 (自安全)
|
|
|
|
|
+//
|
|
|
|
|
+// 后一条保证 scanWithPKID (RLock) → 回调 DeleteRow (Lock) 这类嵌套不卡死:
|
|
|
|
|
+// 同 goroutine 自己持有的读锁不阻塞自己的写锁请求 —— 单 goroutine 不可能并发改坏结构。
|
|
|
|
|
+//
|
|
|
|
|
+// 跨 goroutine:
|
|
|
|
|
+//
|
|
|
|
|
+// 写锁等 (owner==0 && readers - totalSelfReaders == 0) —— 只等**别的 goroutine** 的读者
|
|
|
|
|
+// 读锁等 (owner==0)
|
|
|
|
|
+//
|
|
|
|
|
+// 内部 mu 只用于短暂保护 owner/depth/readers 字段与 cond 等待, 绝不跨临界区持有。
|
|
|
type accessLock struct {
|
|
type accessLock struct {
|
|
|
- mu sync.Mutex
|
|
|
|
|
- cond *sync.Cond // 懒初始化; 跨 goroutine 等待 owner 交锁
|
|
|
|
|
- owner int64 // 持锁 goroutine ID(0 = 无人持有)
|
|
|
|
|
- depth int // 重入深度
|
|
|
|
|
|
|
+ mu sync.Mutex
|
|
|
|
|
+ cond *sync.Cond // 懒初始化
|
|
|
|
|
+ owner int64 // 写锁持有者 goroutine ID (0 = 无人持有)
|
|
|
|
|
+ depth int // 写锁重入深度
|
|
|
|
|
+ readers int32 // 全局读锁总数 (含各 goroutine 自己的)
|
|
|
|
|
+ rDepths map[int64]int // 每个 goroutine 自己的读锁深度
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Lock 获取锁:持有者重入仅加深 depth;否则阻塞直到真正拿到。
|
|
|
|
|
|
|
+// Lock 获取写锁。
|
|
|
|
|
+// - 同 goroutine 已持有写锁 → depth++, 直接返回 (可重入)
|
|
|
|
|
+// - 同 goroutine 已持有自己的读锁 → 直接跳过 (自安全, 避免 RLock→Lock 死锁)
|
|
|
|
|
+// - 否则阻塞, 直到 owner==0 且 readers==0 (没有其他 goroutine 在读)
|
|
|
func (a *accessLock) Lock() {
|
|
func (a *accessLock) Lock() {
|
|
|
g := goroutineID()
|
|
g := goroutineID()
|
|
|
a.mu.Lock()
|
|
a.mu.Lock()
|
|
|
if a.cond == nil {
|
|
if a.cond == nil {
|
|
|
a.cond = sync.NewCond(&a.mu)
|
|
a.cond = sync.NewCond(&a.mu)
|
|
|
|
|
+ if a.rDepths == nil {
|
|
|
|
|
+ a.rDepths = make(map[int64]int)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ // 同 goroutine 已持有写锁 → 重入
|
|
|
if a.owner == g {
|
|
if a.owner == g {
|
|
|
a.depth++
|
|
a.depth++
|
|
|
a.mu.Unlock()
|
|
a.mu.Unlock()
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
- // 跨 goroutine:在 cond 上等待, 直到当前持有者交锁 (owner 归零)。
|
|
|
|
|
- // Wait 会临时释放 mu, 被唤醒时重新持有, 因此用 for 复检以防虚假唤醒 / 被他人抢先。
|
|
|
|
|
- for a.owner != 0 {
|
|
|
|
|
|
|
+ // 同 goroutine 已持有自己的读锁 → 跳过 (单 goroutine 自安全)
|
|
|
|
|
+ if selfDepth, ok := a.rDepths[g]; ok && selfDepth > 0 {
|
|
|
|
|
+ a.mu.Unlock()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 等待: 别人持有写锁 OR readers > 0
|
|
|
|
|
+ // readers 里**已经扣掉了**自己持有的 (因为上面已经 return 如果自己有), 所以直接等
|
|
|
|
|
+ for a.owner != 0 || a.readers > 0 {
|
|
|
a.cond.Wait()
|
|
a.cond.Wait()
|
|
|
}
|
|
}
|
|
|
a.owner = g
|
|
a.owner = g
|
|
@@ -65,7 +93,7 @@ func (a *accessLock) Lock() {
|
|
|
a.mu.Unlock()
|
|
a.mu.Unlock()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// Unlock 释放一层:depth 归零才真正交锁并唤醒等待者。
|
|
|
|
|
|
|
+// Unlock 释放一层写锁。depth 归零才真正交锁并唤醒所有等待者。
|
|
|
func (a *accessLock) Unlock() {
|
|
func (a *accessLock) Unlock() {
|
|
|
g := goroutineID()
|
|
g := goroutineID()
|
|
|
a.mu.Lock()
|
|
a.mu.Lock()
|
|
@@ -79,18 +107,85 @@ func (a *accessLock) Unlock() {
|
|
|
return
|
|
return
|
|
|
}
|
|
}
|
|
|
a.owner = 0
|
|
a.owner = 0
|
|
|
|
|
+ a.depth = 0
|
|
|
if a.cond != nil {
|
|
if a.cond != nil {
|
|
|
- a.cond.Broadcast() // 唤醒全部等待者, 它们按 for 复检重新竞争
|
|
|
|
|
|
|
+ a.cond.Broadcast()
|
|
|
}
|
|
}
|
|
|
a.mu.Unlock()
|
|
a.mu.Unlock()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// lockOp 是公开操作入口的便捷写法:
|
|
|
|
|
-//
|
|
|
|
|
-// defer db.lockOp()()
|
|
|
|
|
-//
|
|
|
|
|
-// db.lockOp() 在本行立即执行(拿到锁),返回的解锁函数在 defer 出作用域时调用。
|
|
|
|
|
|
|
+// RLock 获取读锁。
|
|
|
|
|
+// - 同 goroutine 已持有写锁 → 直接返回 (写锁已独占, 读安全)
|
|
|
|
|
+// - 同 goroutine 已有自己的读锁 → rDepths[g]++, readers++ (读可重入)
|
|
|
|
|
+// - 否则等待写锁释放, 然后 readers++, rDepths[g]=1
|
|
|
|
|
+func (a *accessLock) RLock() {
|
|
|
|
|
+ g := goroutineID()
|
|
|
|
|
+ a.mu.Lock()
|
|
|
|
|
+ if a.cond == nil {
|
|
|
|
|
+ a.cond = sync.NewCond(&a.mu)
|
|
|
|
|
+ if a.rDepths == nil {
|
|
|
|
|
+ a.rDepths = make(map[int64]int)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ if a.owner == g {
|
|
|
|
|
+ // 同 goroutine 持有写锁, 读一定安全, 跳过
|
|
|
|
|
+ a.mu.Unlock()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 同 goroutine 读锁重入
|
|
|
|
|
+ if a.rDepths[g] > 0 {
|
|
|
|
|
+ a.rDepths[g]++
|
|
|
|
|
+ a.readers++
|
|
|
|
|
+ a.mu.Unlock()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ // 等待: 写锁被别人持有
|
|
|
|
|
+ for a.owner != 0 {
|
|
|
|
|
+ a.cond.Wait()
|
|
|
|
|
+ }
|
|
|
|
|
+ a.rDepths[g] = 1
|
|
|
|
|
+ a.readers++
|
|
|
|
|
+ a.mu.Unlock()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// RUnlock 释放当前 goroutine 的一层读锁。
|
|
|
|
|
+// 自己的 rDepth 归零才从 map 删除。全局 readers 归零时唤醒等待写锁的 goroutine。
|
|
|
|
|
+func (a *accessLock) RUnlock() {
|
|
|
|
|
+ g := goroutineID()
|
|
|
|
|
+ a.mu.Lock()
|
|
|
|
|
+ if a.rDepths == nil || a.rDepths[g] <= 0 {
|
|
|
|
|
+ a.mu.Unlock()
|
|
|
|
|
+ return
|
|
|
|
|
+ }
|
|
|
|
|
+ a.readers--
|
|
|
|
|
+ a.rDepths[g]--
|
|
|
|
|
+ if a.rDepths[g] == 0 {
|
|
|
|
|
+ delete(a.rDepths, g)
|
|
|
|
|
+ }
|
|
|
|
|
+ if a.readers == 0 && a.cond != nil {
|
|
|
|
|
+ a.cond.Broadcast()
|
|
|
|
|
+ }
|
|
|
|
|
+ a.mu.Unlock()
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+// 便捷入口 (调用点用 defer xxx()())
|
|
|
|
|
+// ============================================================================
|
|
|
|
|
+
|
|
|
|
|
+// lockOp 写锁便捷写法 —— 原有接口不变, 内部用 Lock。
|
|
|
func (db *DB) lockOp() func() {
|
|
func (db *DB) lockOp() func() {
|
|
|
db.accMu.Lock()
|
|
db.accMu.Lock()
|
|
|
return db.accMu.Unlock
|
|
return db.accMu.Unlock
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// lockRead 读锁便捷写法 —— 纯查询/读路径用这个, 多个 goroutine 可并行。
|
|
|
|
|
+func (db *DB) lockRead() func() {
|
|
|
|
|
+ db.accMu.RLock()
|
|
|
|
|
+ return db.accMu.RUnlock
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// lockWrite 显式写锁便捷写法 (和 lockOp 等价, 命名更明确)。
|
|
|
|
|
+func (db *DB) lockWrite() func() {
|
|
|
|
|
+ db.accMu.Lock()
|
|
|
|
|
+ return db.accMu.Unlock
|
|
|
|
|
+}
|