Mutex

A Mutex is a flag that can be held by at most one goroutine at at time. Its name is derived from mutually exclusive, which indicates its purpose. The raison d’etre of a Mutex is synchronization, by preventing that no other goroutine can perform an operation over the resources that are being mutexed.

It fulfills the Locker interface, thus it defines Lock and Unlock.

It's important to note that a lock doesn't belong to a particular goroutine. Thus, it's allowed for a goroutine to lock the Mutex and another to Unlock it. Therefore, it's still important how the different goroutines interact with the resource in order both to avoid deadlocks but also to avoid accessing to a resource when it's not locked.

Lock

This is how Lock is implemented. But the real meat lies in lockSlow, which we won't cover here. I'm mostly copying the definition just to see how the pieces are put together. The complete implementation is quite complicated, and it involves assembly code in the very end.

func (m *Mutex) Lock() {
	// Fast path: grab unlocked mutex.
	if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
		if race.Enabled {
			race.Acquire(unsafe.Pointer(m))
		}
		return
	}
	m.lockSlow()
}

If the Mutex is already locked, then the goroutine that calls Lock will block until the Mutex gets released, and can be locked by it.

Unlock

This method is responsible for unlocking the Mutex. It can be called from any goroutine, and it's not necessary that is the one that locked it. However, if it's unlocked, it cannot be unlocked right away. Otherwise, it panics. The implementation of this behavior is as follows:

const (
    mutexLocked = 1 << iota // mutex is locked
)

func (m *Mutex) unlockSlow(new int32) {
	if (new+mutexLocked)&mutexLocked == 0 {
		throw("sync: unlock of unlocked mutex")
    }
    /*
    ...
    */
}