The purpose of a lock is to protect a shared resource, or to ensure that the shared resource does not change at the moment. When two or more Goroutines use a shared resource, the lock must be used to protect the shared resource.

Mutex: A shared resource can be used only after it is unlocked

Read-write lock: This has two sets of functions: write lock and read lock.

Read/write lock features: Concurrent read, but no concurrent write, and no read/write.

The mutex

1. Data structure of Mutex

A mutex is essentially a structure

type Mutex struct{

  state int32

  sema uint32

}

State indicates the status of the mutex, such as whether it is locked

Sema stands for semaphore, coroutines block waiting for this semaphore, unlocked coroutines release the semaphore, thus waking up other coroutines waiting for the semaphore

State is a 32-bit integer variable that is internally divided into four parts to record the four states of Mutex.

Locked: indicates whether the Mutex is Locked. 0 is not Locked, and 1 is Locked.

Worken: indicates whether any coroutines have been woken up, 0 indicates that no coroutines have been woken up, 1 indicates that coroutines have been woken up and are being locked (explained later)

Medicine: Indicates whether the lock is Starving. 0 indicates that the lock is not Starving and 1 indicates that the lock is Starving. It means that the coroutine is blocked for more than 1ms.

Waiter: Indicates the number of blocked coroutines waiting for the lock to be unlocked to determine whether the semaphore needs to be released.

Locking between coroutines is actually a locking fight for the right to assign values to Locked. Locking is successful if you set 1 to Locked. Once the coroutine holding the lock is unlocked, the coroutines waiting for the lock will wake up in turn.

Mutex provides two external methods: lock and unlock

Lock() : Lock method

Unlock() : Unlock method

And there are two cases when each method is executed

2. Add the unlocking process

1) Simple locking

Assume that only one coroutine is currently being locked and no other coroutines. During the locking process, the Locked bit is checked to see if it is 0. If the Locked bit is 0, the Locked bit is set to 1, indicating that the lock is successful. The other bits don’t change.

2) The lock is blocked

Assume that another process is now trying to snatch the lock as follows.

After that, coroutine B will attempt to acquire the lock. When coroutine B detects that the Locked flag bit is 1, it will not be able to obtain the lock. When the Waiter count is increased by 1, coroutine B will be blocked until the Locked flag becomes 1.

3) Simple unlock

Assuming no other coroutines block, the unlocking process is as follows.

When coroutine A is unlocked, it will check if Waiter is waiting for the lock. Of course, in this case, there is no Waiter bit 0. There are no other coroutines waiting for the lock, so there is no need to release the semaphore when the coroutine IS unlocked

4) Release semaphore after unlocking

After coroutine A is Locked, Locked is set to 1. When coroutine B is ready to lock and finds that Locked is 1 (Mutex has been preempted), coroutine B is blocked and Waiter +1 is counted. When coroutine A is unlocked, there are two steps: Check if Waiter >0, release a semaphore, and wake up a blocked coroutine. The awakened coroutine B will be Locked at position 1.

3. Spin process

If the Locked bit is 1, Mutex is being used by other coroutines. The coroutine attempting to lock does not block immediately, but waits for a while to check whether the Locked bit is 0. This is called the spin process.

The optional time is short, and if the lock is found to have been released, the coroutine can immediately acquire the lock. Even if a coroutine is awakened, the lock cannot be acquired and can only be blocked again.

The advantage of this option is that instead of immediately going into blocking when the lock fails, there is a reasonable chance that the lock will be acquired, thus avoiding coroutine switching.

1) What is spin

The spin corresponds to the CPU’s PAUSE instruction, which is the equivalent of the CPU idling and “sleeping” for a short time for the program. The current implementation is 30 clock cycles.

The PAUSE instructions are executed at two consecutive probe intervals, which, unlike sleep, do not require the coroutine to be put to sleep.