Golang面试题三(互斥锁)

目录

1.互斥锁的实现原理

2.Mutex的正常模式和饥饿模型的区别

正常模式(非公平锁) 

饥饿模式(公平锁)

总结

3.互斥锁允许自旋的条件

4.读写锁的实现原理

​5.如何实现一个可重入锁

6.Go中的原子操作有哪些

原子操作类型

原子操作函数

7.原子操作和锁的区别


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中的原子操作有哪些

原子操作类型

  • 整型int32int64
  • 无符号整型uint32uint64uintptr
  • 浮点型float64
  • 指针类型*T (T 为任何类型)

原子操作函数

  • Load: 从内存位置加载值。

    • LoadInt32int32 LoadInt32(*int32)
    • LoadInt64int64 LoadInt64(*int64)
    • LoadUint32uint32 LoadUint32(*uint32)
    • LoadUint64uint64 LoadUint64(*uint64)
    • LoadUintptruintptr LoadUintptr(*uintptr)
    • LoadFloat64float64 LoadFloat64(*float64)
    • LoadPtrunsafe.Pointer LoadPtr(*unsafe.Pointer)
  • Store: 将值存储到内存位置。

    • StoreInt32func StoreInt32(*int32, int32)
    • StoreInt64func StoreInt64(*int64, int64)
    • StoreUint32func StoreUint32(*uint32, uint32)
    • StoreUint64func StoreUint64(*uint64, uint64)
    • StoreUintptrfunc StoreUintptr(*uintptr, uintptr)
    • StoreFloat64func StoreFloat64(*float64, float64)
    • StorePtrfunc StorePtr(*unsafe.Pointer, unsafe.Pointer)
  • Swap: 原子交换内存位置的值并返回旧值。

    • SwapInt32int32 SwapInt32(*int32, int32)
    • SwapInt64int64 SwapInt64(*int64, int64)
    • SwapUint32uint32 SwapUint32(*uint32, uint32)
    • SwapUint64uint64 SwapUint64(*uint64, uint64)
    • SwapUintptruintptr SwapUintptr(*uintptr, uintptr)
    • SwapFloat64float64 SwapFloat64(*float64, float64)
    • SwapPtrunsafe.Pointer SwapPtr(*unsafe.Pointer, unsafe.Pointer)
  • CompareAndSwap: 比较并交换内存位置的值。

    • CompareAndSwapInt32int32 CompareAndSwapInt32(*int32, int32, int32)
    • CompareAndSwapInt64int64 CompareAndSwapInt64(*int64, int64, int64)
    • CompareAndSwapUint32uint32 CompareAndSwapUint32(*uint32, uint32, uint32)
    • CompareAndSwapUint64uint64 CompareAndSwapUint64(*uint64, uint64, uint64)
    • CompareAndSwapUintptruintptr CompareAndSwapUintptr(*uintptr, uintptr, uintptr)
    • CompareAndSwapPtrunsafe.Pointer CompareAndSwapPtr(*unsafe.Pointer, unsafe.Pointer, unsafe.Pointer)
  • Add: 原子地增加内存位置的值。

    • AddInt32int32 AddInt32(*int32, int32)
    • AddInt64int64 AddInt64(*int64, int64)
    • AddUint32uint32 AddUint32(*uint32, uint32)
    • AddUint64uint64 AddUint64(*uint64, uint64)
    • AddUintptruintptr 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
}

7.原子操作和锁的区别

  • 45
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值