Go 锁扩展

TryLock

  • 源码中自带的(我的go是 1.20版本)
  • TryLock 会尝试获取锁,如果获取不到返回false,并不会进行休眠阻塞(和 Lock的主要区别)
func (m *Mutex) TryLock() bool {
	old := m.state
    // 如果被锁或者进入饥饿模式直接放弃
	if old&(mutexLocked|mutexStarving) != 0 {
		return false
	}

    //竞争锁失败
	if !atomic.CompareAndSwapInt32(&m.state, old, old|mutexLocked) {
		return false
	}

	if race.Enabled {
		race.Acquire(unsafe.Pointer(m))
	}
	return true
}

统计 goroutine数量

  • 由于state 是sync.Mutex的第一个属性,所以可以通过 unsafe.Pointer(&m.Mutex) 获取
  • state 含义请看文章 Go锁演进 (第一位代表锁状态,第二位代表唤醒状态,第三位代表饥饿状态,其余代表goroutine数量)
package main

import (
	"fmt"
	"sync"
	"sync/atomic"
	"time"
	"unsafe"
)

const (
	mutexLocked = 1 << iota // mutex is locked
	mutexWoken
	mutexStarving
	mutexWaiterShift = iota
)

type Mutex struct {
	sync.Mutex
}

//获取goroutine数
func (m *Mutex) GetGoroutineNumber() int {
    //由于state是sync.Mutex的第一个属性,所以可以通过 unsafe.Pointer(&m.Mutex) 获取
	val := atomic.LoadUint32((*uint32)(unsafe.Pointer(&m.Mutex)))
	return int(val&mutexLocked + val>>mutexWaiterShift)
}

var m Mutex

func main() {
	for i := 0; i < 10; i++ {
		go func() {
			m.Lock()
			time.Sleep(2 * time.Second)
			m.Unlock()
		}()
	}

	go func() {
		ticker := time.NewTicker(1 * time.Second)
		defer ticker.Stop()
		for {
			select {
			case <-ticker.C:
				fmt.Println(m.GetGoroutineNumber())
			}
		}
	}()

	time.Sleep(30 * time.Second)
}

读写锁

  • 读写锁采用的是写锁优先的模式
  • 当获取写锁时(T1时刻),如果在T1之前已经有goroutine获取到读锁, 写锁进入阻塞等待,等待T1之前的读锁全部释放后再唤醒。T1之后的读锁会全部阻塞进入等待,等待写锁释放在执行读锁

读锁

  • readerCount 为负数代表有写锁等待
  • 有写锁等待的情况下, readerCount 为负数,readerWait 为正 (看读锁的 Lock 逻辑)
  • 在写锁等待的情况下, readerCount + 1, readerWait -1
type RWMutex struct {
	w           Mutex  // held if there are pending writers
	writerSem   uint32 // semaphore for writers to wait for completing readers
	readerSem   uint32 // semaphore for readers to wait for completing writers
	readerCount int32  // number of pending readers
	readerWait  int32  // number of departing readers
}

func (rw *RWMutex) RLock() {
    //竞态忽略
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}

    //如果当前的reader等待数 +1 < 0,说明有写操作需要获取锁,阻塞读,等待唤醒
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		runtime_SemacquireMutex(&rw.readerSem, false, 0)
	}

    //竞态忽略
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
	}
}

func (rw *RWMutex) RUnlock() {
    //竞态忽略
	if race.Enabled {
		_ = rw.w.state
		race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
		race.Disable()
	}
	if r := atomic.AddInt32(&rw.readerCount, -1); r < 0 {
		//有写等待
		rw.rUnlockSlow(r)
	}
    //竞态忽略
	if race.Enabled {
		race.Enable()
	}
}

func (rw *RWMutex) rUnlockSlow(r int32) {
    //重复解锁情况下
	if r+1 == 0 || r+1 == -rwmutexMaxReaders {
		race.Enable()
		throw("sync: RUnlock of unlocked RWMutex")
	}
	
	if atomic.AddInt32(&rw.readerWait, -1) == 0 {
		//如果写之前的读都完成了。那么写可以开始干活了
		runtime_Semrelease(&rw.writerSem, false, 1)
	}
}

写锁

  • 写锁获取锁之前,发现还有读锁,会将 readerCount - rwmutexMaxReaders 得到一个 负值 readerCount代表写锁等待
  • 写锁释放后,会将 readerCount + rwmutexMaxReaders 变成写锁等待状态
func (rw *RWMutex) Lock() {
    //竞态忽略
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	//写锁复用的sync.Mutex
	rw.w.Lock()
	
    //变成负的来表示写操作要入场了 
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders
	//读还在占用锁,写还是需要等待,维护写操作需要等待的读操作数量(readerWait)
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)
	}
     //竞态忽略
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
		race.Acquire(unsafe.Pointer(&rw.writerSem))
	}
}

func (rw *RWMutex) Unlock() {
     //竞态忽略
	if race.Enabled {
		_ = rw.w.state
		race.Release(unsafe.Pointer(&rw.readerSem))
		race.Disable()
	}

	//重复解锁
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	if r >= rwmutexMaxReaders {
		race.Enable()
		throw("sync: Unlock of unlocked RWMutex")
	}
	//把写期间的goroutine给他调用了
	for i := 0; i < int(r); i++ {
		runtime_Semrelease(&rw.readerSem, false, 0)
	}
	// Allow other writers to proceed.
	rw.w.Unlock()

     //竞态忽略
	if race.Enabled {
		race.Enable()
	}
}

常见死锁情况

写锁重入

读写锁的的写锁是基于 sync.Mutex

package main

import (
	"sync"
)

var s sync.RWMutex

func main() {
	s.Lock()
	s.Lock()
}
写锁中调用读锁

在 Rlock 后面的 Lock 会阻塞等待 RUnlock,而 RUnlock又被 Lock阻塞,故此死锁

	s.RLock()
	s.Lock()
	s.RUnlock()
	s.Unlock()
循环依赖
  • 16 行程序开始获取到读锁(第一个读)
  • 27 行程序 1秒后写锁入场,写锁依赖 16行中的Rlock(等待第一个读释放锁)
  • 18 行程序2秒后读锁入场,读锁依赖27行的 Lock(等待写获取锁并释放)
  • 16 行程序想解锁,依赖 18行的读锁 (等待第二个锁先释放(第二个读是在写锁等待之后入场,所以会阻塞),然后才能释放第一个锁)

上面就是下面代码死锁流程

package main

import (
	"fmt"
	"sync"
	"time"
)

var s sync.RWMutex
var w sync.WaitGroup

func main() {
	w.Add(3)

	go func() {
		s.RLock()
		time.Sleep(2 * time.Second)
		s.RLock()
		w.Done()
		s.RUnlock()
		w.Done()
		s.RUnlock()
	}()

	go func() {
		time.Sleep(1 * time.Second)
		s.Lock()
		w.Done()
		s.Unlock()
	}()

	w.Wait()
	fmt.Println("凉凉")
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值