golang sync mutex 互斥锁 (1)
2025-09-18 14:05:14
package main
import (
"fmt"
"runtime"
"sync/atomic"
"time"
)
type SpinLock struct {
State uint32
}
func (s *SpinLock) Lock() {
//cas原子操作 自旋锁
spin := 1 // 初始自旋次数
maxSpin := 64 // 最大自旋次数
blockThreshold := 128 // 自旋次数超过这个阈值,就进入阻塞
for attempt := 0; ; attempt++ {
if atomic.CompareAndSwapUint32(&s.State, 0, 1) {
return
}
// 指数退避自旋
for i := 0; i < spin; i++ {
runtime.Gosched() // 让出 CPU
}
// 增加自旋次数
if spin < maxSpin {
// 左移*2
spin <<= 1
}
// 超过阈值,进入阻塞
if attempt > blockThreshold {
// 这里短暂休眠,模拟阻塞等待=> 自旋 + 阻塞混合锁通常不是严格公平
//time.Sleep(time.Microsecond)
//runtime_Semrelease 阻塞队列
}
//当前 G 主动让出 CPU:
//调度器把它从正在执行的状态摘下来。
//放回到可运行队列:
//它会被重新放入 当前 P 的本地队列(runq)。
//如果本地队列满了,可能会放到 全局队列。
//M 继续找下一个 G 来执行:
//M 不会闲着,它会从 P 的本地队列里挑下一个 G 来跑。
//如果本地没有,就去全局队列或者别的 P 偷任务(work stealing)
//当前 goroutine 主动停止运行,交还 CPU。
//调度器会安排 别的 goroutine 上来运行。
//当前 goroutine 不会消失,只是退回到 可运行队列,等下次被调度器挑中再继续跑
// runtime.Gosched() 和 time.Sleep() 区别
//runtime.Gosched()
//G 仍然在 就绪态,只是重新回到 run queue。
//很快会被调度回来执行。
//time.Sleep()
//G 会进入休眠态,调度器不会把它放到 run queue。
//直到时间到,才重新进入 run queue。
//当 goroutine 调用 time.Sleep(d) 时:
//当前 G 从 M 上摘下来
//调度器会把正在运行的 goroutine(G)标记为“休眠中”。
//它不会再占用 CPU。
//放入 Timer 结构里
//Go 的 runtime 里有个 timer heap(小根堆),专门用来管理定时器事件。
//这个 G 会被关联到一个 timer,记录它的唤醒时间(now + d)。
//调度器切走
//当前 M 会去执行别的 G(从 P 的 runq 或全局队列里拿)。
//休眠的 G 不会出现在 runq 里。
//时间到 → 放回 runq
//runtime 的 timer 系统会检查堆顶的 timer。
//当时间到达,就把对应的 G 放回到某个 P 的 runq(本地队列)。
//调度器就能重新挑它来执行。
}
}
func (s *SpinLock) UnLock() {
if atomic.LoadUint32(&s.State) == 0 {
panic("unlock of unlocked SpinLock")
}
atomic.StoreUint32(&s.State, 0)
}
// 尝试上锁
func (s *SpinLock) TryLock() bool {
return atomic.CompareAndSwapUint32(&s.State, 0, 1)
}
func main() {
var s SpinLock
go func() {
s.Lock()
fmt.Println("g1 locked")
time.Sleep(200 * time.Millisecond)
s.UnLock()
fmt.Println("g1 unlocked")
}()
time.Sleep(50 * time.Millisecond)
if s.TryLock() {
fmt.Println("main got lock")
s.UnLock()
} else {
fmt.Println("main failed to get lock")
}
time.Sleep(300 * time.Millisecond)
}
评论