当前位置:网站首页>Important knowledge of golang: mutex
Important knowledge of golang: mutex
2022-06-23 15:24:00 【yue_ xin_ tech】
Abstract
Go It is said to be born for high concurrency , In high concurrency scenarios , It is bound to involve competition for public resources . When the corresponding scene occurs , We often use mutex Of Lock() and Unlock() Methods to occupy or release resources . Although the call is simple , but mutex But it involves a lot of . today , Let's study it carefully .
mutex A preliminary understanding
mutex The source code is mainly in src/sync/mutex.go In the document , Its structure is relatively simple , as follows :
type Mutex struct {
state int32
sema uint32
}
We can see that there is a field sema, It represents the semaphore marker bit . The so-called semaphore is used for Goroutine Blocked or awakened between . This is a bit like the operating system PV The original language operation , Let's meet first PV Primitive operations :
PV The original language explains :
By operating semaphores S To deal with the synchronization and mutual exclusion between processes .
S>0: Express S Resources available ;S=0 No resources are available ;S<0 The absolute number of processes or queues in a linked list . Semaphore S The initial value of should be greater than or equal to 0.
P The original language : Apply for a resource , Yes S Atomic subtraction 1, if reduce 1 Still after S>=0, Then the process continues ; if reduce 1 after S<0, Indicates that no resources are available , You need to block yourself up , Put it in the waiting queue .
V The original language : To release a resource , Yes S Atomic addition 1; if Add 1 after S>0, Then the process continues ; if Add 1 after S<=0, Indicates that there are waiting processes on the waiting queue , Need to wake up the first waiting process .
Through the above explanation ,mutex You can use semaphores to realize goroutine Blocking and arousing .
Actually mutex In essence, it is about Semaphore Of Blocking arousal operation .
When goroutine When the lock resource cannot be occupied, it will be blocked and suspended , The following code logic cannot be executed at this time .
When mutex When the lock resource is released , Will continue to evoke the previous goroutine To preempt lock resources .
as for mutex Of state The status field is used for status flow , These state values involve some concepts , Let's explain it in detail .
mutex Status flags
mutex Of state Yes 32 position , It's low 3 Bits denote respectively 3 States : Wake up state 、 Locked state 、 Starvation , The remaining bits represent the number of currently blocked waits goroutine Number .
mutex According to the current state State to enter Normal mode 、 Hunger mode Or is it The spin .
mutex Normal mode
When mutex call Unlock() Method to release the lock resource , If you find something waiting to be aroused Goroutine When queuing , Will lead the team Goroutine evoke .
Team leader goroutine After being aroused , Would call CAS Methods to try to modify state state , If the modification is successful , It means that the lock resource is occupied successfully .
( notes :CAS stay Go In the use atomic.CompareAndSwapInt32(addr *int32, old, new int32) Method realization ,CAS Similar to the optimistic locking effect , Before modifying, you will first determine whether the address value is still old value , Only or old value , Will continue to be modified to new value , Otherwise it will return to false Indicates that the modification failed .)
mutex Hunger mode
Because of the above Goroutine Arousal does not directly occupy resources , You also need to call CAS The way to Try Occupy lock resources . If there are new arrivals Goroutine, Then it will also call CAS Methods to try to occupy resources .
But for the Go In terms of the scheduling mechanism , Will be more inclined to CPU Occupying for a short time Goroutine First run , And this will create a certain probability that the newcomers Goroutine All the way to the lock resource , At this time, the leader of the team Goroutine Will not occupy until , Lead to Starve to death .
In this case ,Go The hunger model is adopted . That is, by judging the team leader Goroutine When resources are still unavailable after a certain period of time , Will be in Unlock When the lock resource is released , Directly give the lock resource to the team leader Goroutine, And change the current status to Hunger mode .
If there is a newcomer in the back Goroutine When it turns out to be a hunger pattern , Will be added directly to the end of the waiting queue .
mutex The spin
If Goroutine The lock resource is occupied for a short time , Then call semaphores every time to block the call goroutine, Will be very waste resources .
So after meeting certain conditions ,mutex Will make the current Goroutine Go to Idle CPU, Call again after idling CAS Methods to try to occupy lock resources , Until the spin condition is not met , Will eventually be added to the waiting queue .
The spin conditions are as follows :
- Not spinning more than 4 Time
- Multicore processor
- GOMAXPROCS > 1
- p Local Goroutine The queue is empty
It can be seen that , The spin condition is quite strict , After all, it consumes CPU Computing power of .
mutex Of Lock() The process
First , If mutex Of state = 0, That is, no one is occupying resources , There is no block waiting to be aroused goroutine. It will call CAS Method to try to possess the lock , Don't do anything else .
If it doesn't meet m.state = 0, Then it is further judged whether it is necessary to spin .
When you don't need to spin, or you still can't get resources after spinning , This will call runtime_SemacquireMutex Semaphore function , Change the current goroutine Block and join the waiting queue .
When a lock resource is released ,mutex In arousing the head of the team goroutine after , Team head goroutine Will try to occupy lock resources , At this time, it may also be with the new goroutine Compete together .
Be the head of the team goroutine When resources are not available , Will go into starvation mode , Directly give the lock resource to the team leader goroutine, Let the newcomers goroutine Block and join the tail of the waiting queue .
The starvation mode will continue until there is no blocking waiting to be aroused goroutine When queuing , It's going to lift .
Unlock The process
mutex Of Unlock() It's relatively simple . alike , It will unlock quickly first , That is, there is no waiting to be aroused goroutine, There is no need to continue to do other actions .
If the current mode is normal , Is simple evoke Team head Goroutine. If it's hunger mode , will direct Give the lock to the team leader Goroutine, Then arouse the team leader Goroutine, Let it continue to run .
mutex Code details
Okay , The above general process has been completed , The detailed code flow will be presented below , Let us know in more detail mutex Of Lock()、Unlock() Method logic .
mutex Lock() Code details :
// Lock mutex Method of locking .
func (m *Mutex) Lock() {
// Quick lock .
if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
return
}
// Quick lock failed , The locking action with more operations will be carried out .
m.lockSlow()
}
func (m *Mutex) lockSlow() {
var waitStartTime int64 // Record the current goroutine The waiting time of
starving := false // Whether you are hungry
awoke := false // Wake up or not
iter := 0 // Number of spins
old := m.state // At present mutex The state of
for {
// At present mutex Is locked , And non starvation mode , And it meets the spin condition
if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
// The wake-up flag has not been set yet
if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
awoke = true
}
runtime_doSpin()
iter++
old = m.state
continue
}
new := old
// If it's not hunger , Then try locking
// If it's starvation , Will not lock , Because of the current goroutine Will be blocked and added to the end of the queue waiting to be recalled
if old&mutexStarving == 0 {
new |= mutexLocked
}
// Number of waiting queues + 1
if old&(mutexLocked|mutexStarving) != 0 {
new += 1 << mutexWaiterShift
}
// If goroutine It used to be hunger mode , It is also set to hunger mode this time
if starving && old&mutexLocked != 0 {
new |= mutexStarving
}
//
if awoke {
// If the status is not as expected , False report
if new&mutexWoken == 0 {
throw("sync: inconsistent mutex state")
}
// The new status value needs to clear the wake-up flag , Because the current goroutine Will lock or re sleep
new &^= mutexWoken
}
// CAS Try to modify the status , If the modification is successful, the lock resource is obtained
if atomic.CompareAndSwapInt32(&m.state, old, new) {
// Non starvation mode , And the lock has not been acquired , It means that the lock acquired this time is ok Of , direct return
if old&(mutexLocked|mutexStarving) == 0 {
break
}
// Calculate according to the waiting time queueLifo
queueLifo := waitStartTime != 0
if waitStartTime == 0 {
waitStartTime = runtime_nanotime()
}
// Come here , Indicates that the lock is not successful
// queueLife = true, It will put goroutine Put it at the head of the waiting queue
// queueLife = false, It will put goroutine Put it at the end of the waiting queue
runtime_SemacquireMutex(&m.sema, queueLifo, 1)
// Whether the calculation conforms to the hunger model , That is, whether the waiting time exceeds a certain time
starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
old = m.state
// Last time it was hunger mode
if old&mutexStarving != 0 {
if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
throw("sync: inconsistent mutex state")
}
delta := int32(mutexLocked - 1<<mutexWaiterShift)
// This time it's not hunger mode, or the next time there's no waiting queue to wake up goroutine 了
if !starving || old>>mutexWaiterShift == 1 {
delta -= mutexStarving
}
atomic.AddInt32(&m.state, delta)
break
}
// This is no longer hunger mode , Clear spin count , Back to for Circular contention lock .
awoke = true
iter = 0
} else {
old = m.state
}
}
if race.Enabled {
race.Acquire(unsafe.Pointer(m))
}
}
mutex Unlock() Code details :
// Unlock Yes mutex Unlock .
// If it hasn't been locked , Call this method to unlock , Will throw a runtime error .
// It will allow in different Goroutine Lock and unlock on the
func (m *Mutex) Unlock() {
if race.Enabled {
_ = m.state
race.Release(unsafe.Pointer(m))
}
// Quick try to unlock
new := atomic.AddInt32(&m.state, -mutexLocked)
if new != 0 {
// Quick unlock failed , The unlocking action with more operations will be performed .
m.unlockSlow(new)
}
}
func (m *Mutex) unlockSlow(new int32) {
// Unlocked state , Throw an exception directly
if (new+mutexLocked)&mutexLocked == 0 {
throw("sync: unlock of unlocked mutex")
}
// Normal mode
if new&mutexStarving == 0 {
old := new
for {
// There is no waiting queue to be invoked
if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
return
}
// Recall the waiting queue and count -1
new = (old - 1<<mutexWaiterShift) | mutexWoken
if atomic.CompareAndSwapInt32(&m.state, old, new) {
runtime_Semrelease(&m.sema, false, 1)
return
}
old = m.state
}
} else {
// Hunger mode , Give the lock directly to the head of the waiting queue goroutine
runtime_Semrelease(&m.sema, true, 1)
}
}
Interested friends can search the official account 「 Read new technology 」, Pay attention to more push articles .
If you can , Just like it by the way 、 Leave a message 、 Under the share , Thank you for your support !
Read new technology , Read more new knowledge .
边栏推荐
猜你喜欢

Volatile~ variables are not visible under multithreading
Redis缓存三大异常的处理方案梳理总结

重卡界销售和服务的“扛把子”,临沂广顺深耕产品全生命周期服务

The work and development steps that must be done in the early stage of the development of the source code of the live broadcasting room

JS里的数组

Babbitt | metauniverse daily must read: meta, Microsoft and other technology giants set up the metauniverse Standards Forum. Huawei and Alibaba joined. NVIDIA executives said that they welcomed partic

Uniswap acquires genie, an NFT transaction aggregator. Will the NFT transaction market change?

golang 重要知识:sync.Once 讲解

JS create an array (literal)

golang 重要知识:RWMutex 读写锁分析
随机推荐
32. compose beautiful touch animation
JS创建一个数组(字面量)
How to solve the problem that iterative semi supervised training is difficult to implement in ASR training? RTC dev Meetup
Uniswap acquires genie, an NFT transaction aggregator. Will the NFT transaction market change?
【DataHub】LinkedIn DataHub学习笔记
[cloud based co creation] intelligent supply chain plan: improve the decision-making level of the supply chain and help enterprises reduce costs and increase efficiency
微信小程序引导用户添加小程序动画页
Unshift() and shift() of JS
2021-05-22
The new version of Alibaba Seata finally solves the idempotence, suspension and empty rollback problems of the TCC mode
golang 重要知识:定时器 timer
力扣解法汇总513-找树左下角的值
Sectigo(Comodo)证书的由来
Gartner最新报告:低代码应用开发平台在国内的发展
Volatile~ variables are not visible under multithreading
Effect evaluation of regression model under credit product quota pricing scenario
Starting from 3, add paging function in the business system
The work and development steps that must be done in the early stage of the development of the source code of the live broadcasting room
Summary of operating system underlying knowledge (interview)
快速排序的簡單理解