Golang的锁机制

Golang的锁机制

学习过操作系统的都知道程序有临界区这个概念,临界区就是程序片段访问临界资源的那部分代码,临界区同一时刻只能有一个线程进行访问,其他线程需要访问的话必须等待资源空闲。那么一般编程语言都会使用锁来进行临界区访问控制。

sync.Mutex 和 sync.RWMutex

Golang 中的有两种锁,为 sync.Mutex 和 sync.RWMutex。

  1. sync.Mutex 互斥锁,只有一种锁:Lock(),它是绝对锁,同一时间只能有一个锁。
  2. sync.RWMutex 读写锁,它有两种锁: RLock() 和 Lock():

RLock() 叫读锁。实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。

Lock() 叫写锁,它是个绝对锁,就是说,如果一旦某人拿到了这个锁,别人就不能再获取此锁了。

底层方法

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
}

type Mutex struct {
	state int32
	sema  uint32
}

func (rw *RWMutex) Lock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	// First, resolve competition with other writers.
	rw.w.Lock()
	// Announce to readers there is a pending writer.
	r := atomic.AddInt32(&rw.readerCount, -rwmutexMaxReaders) + rwmutexMaxReaders //rwmutexMaxReaders: 1 << 30
	// Wait for active readers.
	if r != 0 && atomic.AddInt32(&rw.readerWait, r) != 0 {
		runtime_SemacquireMutex(&rw.writerSem, false, 0)//true的话,放到队列最前面
	}
	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()
	}

	// Announce to readers there is no active writer.
	r := atomic.AddInt32(&rw.readerCount, rwmutexMaxReaders)
	if r >= rwmutexMaxReaders {
		race.Enable()
		fatal("sync: Unlock of unlocked RWMutex")
	}
	// Unblock blocked readers, if any.
	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()
	}
}
 
func (rw *RWMutex) RLock() {
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}
	if atomic.AddInt32(&rw.readerCount, 1) < 0 {
		// A writer is pending, wait for it.
		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 {
		// Outlined slow-path to allow the fast-path to be inlined
		rw.rUnlockSlow(r)//Mutex 的STATE - 1
	}
	if race.Enabled {
		race.Enable()
	}
}

Lock方法将rw锁定为写入状态,禁止其他线程读取或者写入。

Unlock方法解除rw的写入锁状态,如果m未加写入锁会导致运行时错误。

RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。

RUnlock方法解除rw的读取锁状态,如果m未加读取锁会导致运行时错误。

扩展

当写锁阻塞时,新的读锁是无法申请的。
即在 sync.RWMutex 的使用中,一个线程请求了他的写锁(mx.Lock())后,即便它还没有取到该锁(可能由于资源已被其他人锁定),后面所有的读锁的申请,都将被阻塞,只有取写锁的请求得到了锁且用完释放后,读锁才能去取。

如果一个线程因为某种原因,导致得不到CPU运行时间,这种状态被称之为 饥饿。当写锁阻塞时,新的读锁是无法申请的,这可以有效防止写者饥饿。然而,这种机制也禁止了读锁嵌套。读锁嵌套可能造成死锁。
例子

package main
import (
	"fmt"
	"sync"
	"time"
)
func main() {
	rw := new(sync.RWMutex)
	var deadLockCase time.Duration = 1
	go func() {
		time.Sleep(time.Second * deadLockCase)
		fmt.Println("Writer Try")
		rw.Lock()
		fmt.Println("Writer Fetch")
		time.Sleep(time.Second * 1)
		fmt.Println("Writer Release")
		rw.Unlock()
	}()
	fmt.Println("Reader 1 Try")
	rw.RLock()
	fmt.Println("Reader 1 Fetch")
	time.Sleep(time.Second * 2)
	fmt.Println("Reader 2 Try")
	rw.RLock()
	fmt.Println("Reader 2 Fetch")
	time.Sleep(time.Second * 2)
	fmt.Println("Reader 1 Release")
	rw.RUnlock()
	time.Sleep(time.Second * 1)
	fmt.Println("Reader 2 Release")
	rw.RUnlock()
	time.Sleep(time.Second * 2)
	fmt.Println("Done")
}

运行结果
在这里插入图片描述
分析
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值