当前位置:网站首页>sync. Interpretation of mutex source code

sync. Interpretation of mutex source code

2022-07-05 05:24:00 . fried eggs with tomatoes

Unlocked

package main

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

func main() {
    
	x := 0
	now := time.Now()
	var group sync.WaitGroup
	group.Add(1000)
	for i := 0; i < 1000; i++ {
    
		go func() {
    
			x++
			defer group.Done()
		}()
	}
	//  Wait for all goroutine Finish after execution 
	group.Wait()
	fmt.Println(x)
	fmt.Println(time.Now().UnixMilli() - now.UnixMilli())
}

 Insert picture description here

The normal expectation should be 1000; But because we didn't lock it ,goroutineA While writing ,goroutineB I'm writing, too , Read at the same time , There will be two. goroutine Read the same value , Then go ahead and do x++, Only 983

Lock

package main

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

func main() {
    
	x := 0
	now := time.Now()
	var group sync.WaitGroup
	var mutex sync.Mutex
	group.Add(1000)
	for i := 0; i < 1000; i++ {
    
		go func() {
    
			mutex.Lock()
			x++
			defer mutex.Unlock()
			defer group.Done()
		}()
	}
	//  Wait for all goroutine Finish after execution 
	group.Wait()
	fmt.Println(x)
	fmt.Println(time.Now().UnixMilli() - now.UnixMilli())
}

 Insert picture description here

Source code

Mutex

// Mutex It's a kind of mutex .
//  Mutually exclusive 0 Value is an unlocked mutex .
//  Mutexes cannot be copied after the first use 
type Mutex struct {
    
	state int32 //  Indicates the status of the current lock 
	sema  uint32 //  Semaphore variable , Used to control goroutine Blocking sleep and wakeup 
}

// state state 
const (
	mutexLocked = 1 << iota //  Locking state of mutex 
	mutexWoken	//  Wake up from normal mode 
	mutexStarving	//  Currently, the mutex is hungry 
	mutexWaiterShift = iota //  Waiting for the goroutine Number ` 
	starvationThresholdNs = 1e6
)
  • Mutex Two modes of operation
    • Normal mode ( Not fair lock )
      • In normal mode , The waiting person follows FIFO( fifo ) Sequential queuing , But wake up goroutine Don't have a lock , But with the newly arrived goroutines Fight for the ownership of the lock , Newly added goroutines There is an advantage :** They are already CPU Up operation , It could be one or more ,** But it will only awaken one goroutine, Wake up this goroutine It is very likely to lose , under these circumstances , It is in front of the waiting queue . If a person waits longer than 1ms, It will switch from normal mode to hungry mode
    • Hunger mode ( Fair lock )
      • In hunger mode , The ownership of the mutex is directly from the one being unlocked goroutine To the front of the queue goroutine, The new arrival goroutines Will not attempt to acquire the lock , Will not try to spin , They will put themselves at the end of the waiting queue
      • If you get the lock goroutine It's the last one in the queue goroutine, It will switch from starvation mode to normal mode
      • If you get the lock goroutine The waiting time is less than 1ms, It will switch from starvation mode to normal mode
    • The performance of normal mode is much better , Because even if there is blockage goroutines,goroutine You can also obtain locks several times in a row , Hunger mode solves the problem of lock fairness , But performance will degrade , Switching back and forth between normal mode and starvation mode is actually a balance mode between performance and fairness

Mutex.Lock

//  Lock method , If you lock repeatedly, an error will be reported 
func (m *Mutex) Lock() {
    
	//  adopt CAS Judge the status of the current lock ;
    //  If the lock is free , namely m.state==0
    //  Then lock it , namely m.state=1
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
    
		if race.Enabled {
    
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}
	//  The current lock has been locked by another goroutine Get 
	m.lockSlow()
}

func (m *Mutex) lockSlow() {
    
    //  Used to calculate the waiting time 
	var waitStartTime int64 
    //  Hunger mode marker , If the waiting time exceeds 1ms,starving Set to true
    //  Subsequent operations will m Also marked as hungry 
	starving := false 
    //  At present goroutine Whether to wake up 
    //  When goroutine In spin , amount to CPU There are already waiting on goroutine
    //  To avoid m When unlocking, it wakes up others goroutine, Spin with m Set to wake up state 
    // m When awake ,awoke Also set to true
	awoke := false
    //  Number of spins 
	iter := 0
    //  Current lock state 
	old := m.state
	for {
    
		//  Determine whether you can enter spin 
        // 1. Currently in non hunger mode 
        // 2.runtime_canSpin(iter); The number of spins is less than 4
		if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
    
            // !awoke: Judge the present goroutine Not in a wakeup state 
            // old&mutexWoken == 0:  There is currently no other awakening goroutine
            // old>>mutexWaiterShift != 0:  There are waiting in the waiting queue goroutine
            // atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken):  Try to lower the current lock 2 Bit Woken The status bit is set to 1, Indicates that has been awakened , This is to notify when unlocking Unlock() Don't wake up the others goroutine 了 
			if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
				atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
    
                 //  Will the current goroutine It's wake up 
				awoke = true
			}
            //  Spin 
			runtime_doSpin()
            //  Number of spins ++
			iter++
            //  Record the status of the current lock 
			old = m.state
			continue
		}
        //  be based on old Declare a new state 
		new := old
		//  Don't try to get hungry mutexes , The new arrival goroutine You have to line up 
		if old&mutexStarving == 0 {
    
			new |= mutexLocked
		}
        //  If old Locked or in collective mode , according to FIFO line up 
		if old&(mutexLocked|mutexStarving) != 0 {
    
			new += 1 << mutexWaiterShift
		}
		//  If m In hunger mode , will starving Set as 1 Express hunger 
		if starving && old&mutexLocked != 0 {
    
			new |= mutexStarving
		}
		if awoke {
    
			//  Wake up state operation 
			if new&mutexWoken == 0 {
    
                //  Mutually exclusive state is inconsistent , Throw an exception 
				throw("sync: inconsistent mutex state")
			}
            //  The new status clears the wake-up flag 
			new &^= mutexWoken
		}
        //  Try to update the state of the lock to the desired state 
		if atomic.CompareAndSwapInt32(&m.state, old, new) {
    
			if old&(mutexLocked|mutexStarving) == 0 {
    
                //  If the original lock is not locked and is not in hunger mode 
                //  At present goroutine Got the lock , direct break
				break 
			}
			//  The reverse lock has not been obtained ;waitStartTime It's just waiting time 
            //  If not for 0 Just put it on the right side 
			queueLifo := waitStartTime != 0
            //  by 0 Put it at the end of the team 
			if waitStartTime == 0 {
    
				waitStartTime = runtime_nanotime()
			}
            //  Block waiting 
			runtime_SemacquireMutex(&m.sema, queueLifo, 1)
            //  After wake up , Check whether it is in hunger mode 
            // 1. Currently in hunger mode 
            // 2.  Wait more than 1ms
			starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
            //  Once again m The state of 
			old = m.state
			if old&mutexStarving != 0 {
    
				if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
    
                    //  The current lock is neither acquired nor awakened , Or the waiting queue is empty 
                    //  It means that there is a mutually exclusive state inconsistency 
					throw("sync: inconsistent mutex state")
				}
                //  At present goroutine Got the lock , Waiting in line -1
				delta := int32(mutexLocked - 1<<mutexWaiterShift)
				if !starving || old>>mutexWaiterShift == 1 {
    
                    //  There is only one non hungry mode or queue left goroutine Out of hunger mode 
					delta -= mutexStarving
				}
                //  Update the status value and break
				atomic.AddInt32(&m.state, delta)
				break
			}
            //  Set the state to wakeup ; And reset the number of spins 
			awoke = true
			iter = 0
		} else {
    
            //  Lock by others goroutine Occupy , Restore status continues for loop 
			old = m.state
		}
	}

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

Mutex.Unlock

//  Unlocking method , If you unlock without locking, an error will be reported 
func (m *Mutex) Unlock() {
    
	if race.Enabled {
    
		_ = m.state
		race.Release(unsafe.Pointer(m))
	}

	//  take m.state Set as 0, And copy it to new
    //  If new by 0; Indicates successful unlocking ; immediate withdrawal 
	new := atomic.AddInt32(&m.state, -mutexLocked)
	if new != 0 {
    
		// new Not for 0; Indicates that the current lock is not occupied , But there is waiting goroutine Not awakened 
		m.unlockSlow(new)
	}
}

func (m *Mutex) unlockSlow(new int32) {
    
    //  The current lock is not locked , If you unlock it directly, an error will be reported 
	if (new+mutexLocked)&mutexLocked == 0 {
    
		throw("sync: unlock of unlocked mutex")
	}
	if new&mutexStarving == 0 {
    
        //  Normal mode 
		old := new
		for {
    
			//  There is no waiting in the queue goroutine Go straight back to 
            //  If the lock is in the locked state ; On behalf of goroutine Get lock ; Go straight back to 
            //  If the lock is in the wakeup state ; On behalf of goroutine Awakened , Go straight back to 
            //  If you are currently in hunger mode , After locking, it will follow FIFO The rules are for the right goroutine
			if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
    
				return
			}
			//  Wake up the lock , Waiting in line -1
			new = (old - 1<<mutexWaiterShift) | mutexWoken
			if atomic.CompareAndSwapInt32(&m.state, old, new) {
    
                //  A successful preemption wakes up a goroutine return 
				runtime_Semrelease(&m.sema, false, 1)
				return
			}
            //  Preemption is unsuccessful ; Update status information ; continue for
			old = m.state
		}
	} else {
    
		//  Hunger mode 
        //  Directly in accordance with the FIFO The rules are for the right goroutine
		runtime_Semrelease(&m.sema, true, 1)
	}
}
原网站

版权声明
本文为[. fried eggs with tomatoes]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/186/202207050519000371.html