当前位置:网站首页>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 .
边栏推荐
猜你喜欢

百万奖金等你来拿,首届中国元宇宙创新应用大赛联合创业黑马火热招募中!

JS garbage collection

js遍历数组(用forEach()方法)

2021-05-22

百萬獎金等你來拿,首届中國元宇宙創新應用大賽聯合創業黑馬火熱招募中!

mysql 系列:存储引擎

Gartner最新报告:低代码应用开发平台在国内的发展

golang 重要知识:atomic 原子操作

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

golang 重要知识:定时器 timer
随机推荐
JS里的数组
这届文娱人,将副业做成了主业
2021-05-08
PHP 2D array insert
详解Redis分布式锁的原理与实现
基因检测,如何帮助患者对抗疾病?
30. concatenate substrings of all words
JS garbage collection
力扣解法匯總513-找樹左下角的值
golang 重要知识:atomic 原子操作
重卡界销售和服务的“扛把子”,临沂广顺深耕产品全生命周期服务
Un million de bonus vous attend, le premier concours d'innovation et d'application de la Chine Yuan cosmique Joint Venture Black Horse Hot Recruitment!
K8s-- deploy stand-alone MySQL and persist it
Uniswap 收购 NFT交易聚合器 Genie,NFT 交易市场将生变局?
2021-04-15
mysql 系列:总体架构概述
Origin of sectigo (Comodo) Certificate
Simple tutorial of live streaming with OBS
百萬獎金等你來拿,首届中國元宇宙創新應用大賽聯合創業黑馬火熱招募中!
js的slice()和splice()