目录
1.互斥锁的实现原理
2.Mutex的正常模式和饥饿模型的区别
在Go中一共可以分为两种抢锁的模式,一种是正常模式,另外一种是饥饿模式。
正常模式(非公平锁)
在刚开始的时候,是处于正常模式(Barging),当一个G1持有着一个锁的时候,G2会自旋的去尝试获取这个锁。
当自旋超过4次还没有能获取到锁的时候,这个G2就会被加入到获取锁的等待队列里面,并阻塞等待唤醒。
正常模式下,所有等待锁的goroutine按照FIFO(先进先出)顺序等待。唤醒的goroutine不会直接拥有锁,而是会和新请求锁的goroutine竟争锁。新请求锁的goroutine具有优势:它正用CPU上执行,而且可能有好几个,所以刚唤醒的goroutine很大可能竞争失败,长时间获取不到锁切换到饥饿模式。
饥饿模式(公平锁)
当一个goroutine等待锁时间超过1毫秒时,它可能会遇到饥饿问题。在版本1.9中,这种场景下Go Mutex切换到饥饿模式(handoff),解决饥饿问题。
starving = runtime_nanotime() - waitStartTime > le6
饥饿模式下,直接把锁交给等待队列中排在第一位的goroutine(队头),同时饥饿模式下,新进来的goroutine不会参与抢锁,也不会进入自旋状态,会直接进入等待队列的尾部,这样很好的解决了老的goroutine一直抢不到锁的场景。
那么也不可能说永远的保持一个饥饿的状态,总归会有吃饱的时候,也就是总有那么一刻Mutex会回归到正常模式,那么回归正常模式必须具备的条件有以下几种:
1.Go的执行时间小于1毫秒
2.等待队列已经全部清空了
当满足上述两个条件的任意一个的时候,Mutex会切换回正常模式,而Go的抢锁的过程,就是在这个正常模式和饥饿模式中来回切换进行的。
总结
对于两种模式,正常模式性能是最好的,goroutine可以连续多次获取锁,饥饿模型解决了获取锁的公平问题,但性能会下降,是性能和公平的平衡模式。
3.互斥锁允许自旋的条件
4.读写锁的实现原理
5.如何实现一个可重入锁
package main
import (
"fmt"
"github.com/petermattis/goid"
"sync"
"sync/atomic"
)
// ReentryMutex struct
type ReentryMutex struct {
sync.Mutex
owner int64 //当前锁的拥有者 goroutineid
reentry int32 //重入次数
}
func (r *ReentryMutex) Lock() {
gid := goid.Get()
//如果锁的拥有者已经是当前goroutine,记录下他已经重入的次数
if atomic.LoadInt64(&r.owner) == gid {
r.reentry++
return
}
r.Mutex.Lock()
atomic.StoreInt64(&r.owner, gid)
//初始化可访问次数
r.reentry = 1
}
func (r *ReentryMutex) Unlock() {
gid := goid.Get()
//如果解锁协程不是当前协程的拥有者,就panic
if atomic.LoadInt64(&r.owner) != gid {
panic(any(fmt.Sprintf("wrong the owner(%d): %d!", r.owner, gid)))
}
r.reentry--
if r.reentry == 0 {
atomic.StoreInt64(&r.owner, -1)
r.Mutex.Unlock()
}
}
func main() {
var wg sync.WaitGroup
reentryMutex := ReentryMutex{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func(index int) {
defer wg.Done()
reentryMutex.Lock()
defer reentryMutex.Unlock()
fmt.Printf("Goroutine %d acquired the lock.\n", index)
reentryMutex.Lock()
defer reentryMutex.Unlock()
fmt.Printf("Goroutine %d acquired the lock again.\n", index)
}(i)
}
wg.Wait()
fmt.Println("All goroutines have finished.")
}
6.Go中的原子操作有哪些
原子操作类型
- 整型:
int32
,int64
- 无符号整型:
uint32
,uint64
,uintptr
- 浮点型:
float64
- 指针类型:
*T
(T 为任何类型)
原子操作函数
-
Load: 从内存位置加载值。
LoadInt32
:int32 LoadInt32(*int32)
LoadInt64
:int64 LoadInt64(*int64)
LoadUint32
:uint32 LoadUint32(*uint32)
LoadUint64
:uint64 LoadUint64(*uint64)
LoadUintptr
:uintptr LoadUintptr(*uintptr)
LoadFloat64
:float64 LoadFloat64(*float64)
LoadPtr
:unsafe.Pointer LoadPtr(*unsafe.Pointer)
-
Store: 将值存储到内存位置。
StoreInt32
:func StoreInt32(*int32, int32)
StoreInt64
:func StoreInt64(*int64, int64)
StoreUint32
:func StoreUint32(*uint32, uint32)
StoreUint64
:func StoreUint64(*uint64, uint64)
StoreUintptr
:func StoreUintptr(*uintptr, uintptr)
StoreFloat64
:func StoreFloat64(*float64, float64)
StorePtr
:func StorePtr(*unsafe.Pointer, unsafe.Pointer)
-
Swap: 原子交换内存位置的值并返回旧值。
SwapInt32
:int32 SwapInt32(*int32, int32)
SwapInt64
:int64 SwapInt64(*int64, int64)
SwapUint32
:uint32 SwapUint32(*uint32, uint32)
SwapUint64
:uint64 SwapUint64(*uint64, uint64)
SwapUintptr
:uintptr SwapUintptr(*uintptr, uintptr)
SwapFloat64
:float64 SwapFloat64(*float64, float64)
SwapPtr
:unsafe.Pointer SwapPtr(*unsafe.Pointer, unsafe.Pointer)
-
CompareAndSwap: 比较并交换内存位置的值。
CompareAndSwapInt32
:int32 CompareAndSwapInt32(*int32, int32, int32)
CompareAndSwapInt64
:int64 CompareAndSwapInt64(*int64, int64, int64)
CompareAndSwapUint32
:uint32 CompareAndSwapUint32(*uint32, uint32, uint32)
CompareAndSwapUint64
:uint64 CompareAndSwapUint64(*uint64, uint64, uint64)
CompareAndSwapUintptr
:uintptr CompareAndSwapUintptr(*uintptr, uintptr, uintptr)
CompareAndSwapPtr
:unsafe.Pointer CompareAndSwapPtr(*unsafe.Pointer, unsafe.Pointer, unsafe.Pointer)
-
Add: 原子地增加内存位置的值。
AddInt32
:int32 AddInt32(*int32, int32)
AddInt64
:int64 AddInt64(*int64, int64)
AddUint32
:uint32 AddUint32(*uint32, uint32)
AddUint64
:uint64 AddUint64(*uint64, uint64)
AddUintptr
:uintptr AddUintptr(*uintptr, uintptr)
package main
import (
"fmt"
"sync/atomic"
)
func main() {
var count int64 = 0
// 原子增加
atomic.AddInt64(&count, 1)
// 原子加载
value := atomic.LoadInt64(&count)
fmt.Println(value) // 输出: 1
// 原子交换
newValue := atomic.SwapInt64(&count, 10)
fmt.Println(newValue) // 输出: 1
// 比较并交换
oldValue := atomic.CompareAndSwapInt64(&count, 10, 20)
fmt.Println(oldValue) // 输出: true
fmt.Println(atomic.LoadInt64(&count)) // 输出: 20
}