Source File
mutex.go
Belonging Package
internal/sync
// Copyright 2024 The Go Authors. All rights reserved.// Use of this source code is governed by a BSD-style// license that can be found in the LICENSE file.// Package sync provides basic synchronization primitives such as mutual// exclusion locks to internal packages (including ones that depend on sync).//// Tests are defined in package [sync].package syncimport ()// A Mutex is a mutual exclusion lock.//// See package [sync.Mutex] documentation.type Mutex struct {state int32sema uint32}const (mutexLocked = 1 << iota // mutex is lockedmutexWokenmutexStarvingmutexWaiterShift = iota// Mutex fairness.//// Mutex can be in 2 modes of operations: normal and starvation.// In normal mode waiters are queued in FIFO order, but a woken up waiter// does not own the mutex and competes with new arriving goroutines over// the ownership. New arriving goroutines have an advantage -- they are// already running on CPU and there can be lots of them, so a woken up// waiter has good chances of losing. In such case it is queued at front// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,// it switches mutex to the starvation mode.//// In starvation mode ownership of the mutex is directly handed off from// the unlocking goroutine to the waiter at the front of the queue.// New arriving goroutines don't try to acquire the mutex even if it appears// to be unlocked, and don't try to spin. Instead they queue themselves at// the tail of the wait queue.//// If a waiter receives ownership of the mutex and sees that either// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,// it switches mutex back to normal operation mode.//// Normal mode has considerably better performance as a goroutine can acquire// a mutex several times in a row even if there are blocked waiters.// Starvation mode is important to prevent pathological cases of tail latency.starvationThresholdNs = 1e6)// Lock locks m.//// See package [sync.Mutex] documentation.func ( *Mutex) () {// Fast path: grab unlocked mutex.if atomic.CompareAndSwapInt32(&.state, 0, mutexLocked) {if race.Enabled {race.Acquire(unsafe.Pointer())}return}// Slow path (outlined so that the fast path can be inlined).lockSlow()}// TryLock tries to lock m and reports whether it succeeded.//// See package [sync.Mutex] documentation.func ( *Mutex) () bool {:= .stateif &(mutexLocked|mutexStarving) != 0 {return false}// There may be a goroutine waiting for the mutex, but we are// running now and can try to grab the mutex before that// goroutine wakes up.if !atomic.CompareAndSwapInt32(&.state, , |mutexLocked) {return false}if race.Enabled {race.Acquire(unsafe.Pointer())}return true}func ( *Mutex) () {var int64:= false:= false:= 0:= .statefor {// Don't spin in starvation mode, ownership is handed off to waiters// so we won't be able to acquire the mutex anyway.if &(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin() {// Active spinning makes sense.// Try to set mutexWoken flag to inform Unlock// to not wake other blocked goroutines.if ! && &mutexWoken == 0 && >>mutexWaiterShift != 0 &&atomic.CompareAndSwapInt32(&.state, , |mutexWoken) {= true}runtime_doSpin()++= .statecontinue}:=// Don't try to acquire starving mutex, new arriving goroutines must queue.if &mutexStarving == 0 {|= mutexLocked}if &(mutexLocked|mutexStarving) != 0 {+= 1 << mutexWaiterShift}// The current goroutine switches mutex to starvation mode.// But if the mutex is currently unlocked, don't do the switch.// Unlock expects that starving mutex has waiters, which will not// be true in this case.if && &mutexLocked != 0 {|= mutexStarving}if {// The goroutine has been woken from sleep,// so we need to reset the flag in either case.if &mutexWoken == 0 {throw("sync: inconsistent mutex state")}&^= mutexWoken}if atomic.CompareAndSwapInt32(&.state, , ) {if &(mutexLocked|mutexStarving) == 0 {break // locked the mutex with CAS}// If we were already waiting before, queue at the front of the queue.:= != 0if == 0 {= runtime_nanotime()}runtime_SemacquireMutex(&.sema, , 2)= || runtime_nanotime()- > starvationThresholdNs= .stateif &mutexStarving != 0 {// If this goroutine was woken and mutex is in starvation mode,// ownership was handed off to us but mutex is in somewhat// inconsistent state: mutexLocked is not set and we are still// accounted as waiter. Fix that.if &(mutexLocked|mutexWoken) != 0 || >>mutexWaiterShift == 0 {throw("sync: inconsistent mutex state")}:= int32(mutexLocked - 1<<mutexWaiterShift)if ! || >>mutexWaiterShift == 1 {// Exit starvation mode.// Critical to do it here and consider wait time.// Starvation mode is so inefficient, that two goroutines// can go lock-step infinitely once they switch mutex// to starvation mode.-= mutexStarving}atomic.AddInt32(&.state, )break}= true= 0} else {= .state}}if race.Enabled {race.Acquire(unsafe.Pointer())}}// Unlock unlocks m.//// See package [sync.Mutex] documentation.func ( *Mutex) () {if race.Enabled {_ = .staterace.Release(unsafe.Pointer())}// Fast path: drop lock bit.:= atomic.AddInt32(&.state, -mutexLocked)if != 0 {// Outlined slow path to allow inlining the fast path.// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock..unlockSlow()}}func ( *Mutex) ( int32) {if (+mutexLocked)&mutexLocked == 0 {fatal("sync: unlock of unlocked mutex")}if &mutexStarving == 0 {:=for {// If there are no waiters or a goroutine has already// been woken or grabbed the lock, no need to wake anyone.// In starvation mode ownership is directly handed off from unlocking// goroutine to the next waiter. We are not part of this chain,// since we did not observe mutexStarving when we unlocked the mutex above.// So get off the way.if >>mutexWaiterShift == 0 || &(mutexLocked|mutexWoken|mutexStarving) != 0 {return}// Grab the right to wake someone.= ( - 1<<mutexWaiterShift) | mutexWokenif atomic.CompareAndSwapInt32(&.state, , ) {runtime_Semrelease(&.sema, false, 2)return}= .state}} else {// Starving mode: handoff mutex ownership to the next waiter, and yield// our time slice so that the next waiter can start to run immediately.// Note: mutexLocked is not set, the waiter will set it after wakeup.// But mutex is still considered locked if mutexStarving is set,// so new coming goroutines won't acquire it.runtime_Semrelease(&.sema, true, 2)}}
![]() |
The pages are generated with Golds v0.7.9-preview. (GOOS=linux GOARCH=amd64) Golds is a Go 101 project developed by Tapir Liu. PR and bug reports are welcome and can be submitted to the issue list. Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds. |