...

Source file src/pkg/sync/mutex.go

     1	// Copyright 2009 The Go Authors. All rights reserved.
     2	// Use of this source code is governed by a BSD-style
     3	// license that can be found in the LICENSE file.
     4	
     5	// Package sync provides basic synchronization primitives such as mutual
     6	// exclusion locks. Other than the Once and WaitGroup types, most are intended
     7	// for use by low-level library routines. Higher-level synchronization is
     8	// better done via channels and communication.
     9	//
    10	// Values containing the types defined in this package should not be copied.
    11	package sync
    12	
    13	import (
    14		"internal/race"
    15		"sync/atomic"
    16		"unsafe"
    17	)
    18	
    19	func throw(string) // provided by runtime
    20	
    21	// A Mutex is a mutual exclusion lock.
    22	// The zero value for a Mutex is an unlocked mutex.
    23	//
    24	// A Mutex must not be copied after first use.
    25	type Mutex struct {
    26		state int32
    27		sema  uint32
    28	}
    29	
    30	// A Locker represents an object that can be locked and unlocked.
    31	type Locker interface {
    32		Lock()
    33		Unlock()
    34	}
    35	
    36	const (
    37		mutexLocked = 1 << iota // mutex is locked
    38		mutexWoken
    39		mutexStarving
    40		mutexWaiterShift = iota
    41	
    42		// Mutex fairness.
    43		//
    44		// Mutex can be in 2 modes of operations: normal and starvation.
    45		// In normal mode waiters are queued in FIFO order, but a woken up waiter
    46		// does not own the mutex and competes with new arriving goroutines over
    47		// the ownership. New arriving goroutines have an advantage -- they are
    48		// already running on CPU and there can be lots of them, so a woken up
    49		// waiter has good chances of losing. In such case it is queued at front
    50		// of the wait queue. If a waiter fails to acquire the mutex for more than 1ms,
    51		// it switches mutex to the starvation mode.
    52		//
    53		// In starvation mode ownership of the mutex is directly handed off from
    54		// the unlocking goroutine to the waiter at the front of the queue.
    55		// New arriving goroutines don't try to acquire the mutex even if it appears
    56		// to be unlocked, and don't try to spin. Instead they queue themselves at
    57		// the tail of the wait queue.
    58		//
    59		// If a waiter receives ownership of the mutex and sees that either
    60		// (1) it is the last waiter in the queue, or (2) it waited for less than 1 ms,
    61		// it switches mutex back to normal operation mode.
    62		//
    63		// Normal mode has considerably better performance as a goroutine can acquire
    64		// a mutex several times in a row even if there are blocked waiters.
    65		// Starvation mode is important to prevent pathological cases of tail latency.
    66		starvationThresholdNs = 1e6
    67	)
    68	
    69	// Lock locks m.
    70	// If the lock is already in use, the calling goroutine
    71	// blocks until the mutex is available.
    72	func (m *Mutex) Lock() {
    73		// Fast path: grab unlocked mutex.
    74		if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
    75			if race.Enabled {
    76				race.Acquire(unsafe.Pointer(m))
    77			}
    78			return
    79		}
    80		// Slow path (outlined so that the fast path can be inlined)
    81		m.lockSlow()
    82	}
    83	
    84	func (m *Mutex) lockSlow() {
    85		var waitStartTime int64
    86		starving := false
    87		awoke := false
    88		iter := 0
    89		old := m.state
    90		for {
    91			// Don't spin in starvation mode, ownership is handed off to waiters
    92			// so we won't be able to acquire the mutex anyway.
    93			if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
    94				// Active spinning makes sense.
    95				// Try to set mutexWoken flag to inform Unlock
    96				// to not wake other blocked goroutines.
    97				if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
    98					atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
    99					awoke = true
   100				}
   101				runtime_doSpin()
   102				iter++
   103				old = m.state
   104				continue
   105			}
   106			new := old
   107			// Don't try to acquire starving mutex, new arriving goroutines must queue.
   108			if old&mutexStarving == 0 {
   109				new |= mutexLocked
   110			}
   111			if old&(mutexLocked|mutexStarving) != 0 {
   112				new += 1 << mutexWaiterShift
   113			}
   114			// The current goroutine switches mutex to starvation mode.
   115			// But if the mutex is currently unlocked, don't do the switch.
   116			// Unlock expects that starving mutex has waiters, which will not
   117			// be true in this case.
   118			if starving && old&mutexLocked != 0 {
   119				new |= mutexStarving
   120			}
   121			if awoke {
   122				// The goroutine has been woken from sleep,
   123				// so we need to reset the flag in either case.
   124				if new&mutexWoken == 0 {
   125					throw("sync: inconsistent mutex state")
   126				}
   127				new &^= mutexWoken
   128			}
   129			if atomic.CompareAndSwapInt32(&m.state, old, new) {
   130				if old&(mutexLocked|mutexStarving) == 0 {
   131					break // locked the mutex with CAS
   132				}
   133				// If we were already waiting before, queue at the front of the queue.
   134				queueLifo := waitStartTime != 0
   135				if waitStartTime == 0 {
   136					waitStartTime = runtime_nanotime()
   137				}
   138				runtime_SemacquireMutex(&m.sema, queueLifo, 1)
   139				starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
   140				old = m.state
   141				if old&mutexStarving != 0 {
   142					// If this goroutine was woken and mutex is in starvation mode,
   143					// ownership was handed off to us but mutex is in somewhat
   144					// inconsistent state: mutexLocked is not set and we are still
   145					// accounted as waiter. Fix that.
   146					if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
   147						throw("sync: inconsistent mutex state")
   148					}
   149					delta := int32(mutexLocked - 1<<mutexWaiterShift)
   150					if !starving || old>>mutexWaiterShift == 1 {
   151						// Exit starvation mode.
   152						// Critical to do it here and consider wait time.
   153						// Starvation mode is so inefficient, that two goroutines
   154						// can go lock-step infinitely once they switch mutex
   155						// to starvation mode.
   156						delta -= mutexStarving
   157					}
   158					atomic.AddInt32(&m.state, delta)
   159					break
   160				}
   161				awoke = true
   162				iter = 0
   163			} else {
   164				old = m.state
   165			}
   166		}
   167	
   168		if race.Enabled {
   169			race.Acquire(unsafe.Pointer(m))
   170		}
   171	}
   172	
   173	// Unlock unlocks m.
   174	// It is a run-time error if m is not locked on entry to Unlock.
   175	//
   176	// A locked Mutex is not associated with a particular goroutine.
   177	// It is allowed for one goroutine to lock a Mutex and then
   178	// arrange for another goroutine to unlock it.
   179	func (m *Mutex) Unlock() {
   180		if race.Enabled {
   181			_ = m.state
   182			race.Release(unsafe.Pointer(m))
   183		}
   184	
   185		// Fast path: drop lock bit.
   186		new := atomic.AddInt32(&m.state, -mutexLocked)
   187		if new != 0 {
   188			// Outlined slow path to allow inlining the fast path.
   189			// To hide unlockSlow during tracing we skip one extra frame when tracing GoUnblock.
   190			m.unlockSlow(new)
   191		}
   192	}
   193	
   194	func (m *Mutex) unlockSlow(new int32) {
   195		if (new+mutexLocked)&mutexLocked == 0 {
   196			throw("sync: unlock of unlocked mutex")
   197		}
   198		if new&mutexStarving == 0 {
   199			old := new
   200			for {
   201				// If there are no waiters or a goroutine has already
   202				// been woken or grabbed the lock, no need to wake anyone.
   203				// In starvation mode ownership is directly handed off from unlocking
   204				// goroutine to the next waiter. We are not part of this chain,
   205				// since we did not observe mutexStarving when we unlocked the mutex above.
   206				// So get off the way.
   207				if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
   208					return
   209				}
   210				// Grab the right to wake someone.
   211				new = (old - 1<<mutexWaiterShift) | mutexWoken
   212				if atomic.CompareAndSwapInt32(&m.state, old, new) {
   213					runtime_Semrelease(&m.sema, false, 1)
   214					return
   215				}
   216				old = m.state
   217			}
   218		} else {
   219			// Starving mode: handoff mutex ownership to the next waiter.
   220			// Note: mutexLocked is not set, the waiter will set it after wakeup.
   221			// But mutex is still considered locked if mutexStarving is set,
   222			// so new coming goroutines won't acquire it.
   223			runtime_Semrelease(&m.sema, true, 1)
   224		}
   225	}
   226	

View as plain text