Common lock mechanisms in concurrent programming: optimistic lock, pessimistic lock, CAS, spin lock, mutual exclusion lock, read-write lock


Optimistic lock vs. pessimistic lock

Optimistic lock and pessimistic lock are named after their meanings. The difference between them is the mentality of doing things .

Pessimistic lock

Pessimistic locking is more pessimistic. It always believes that shared resources will be modified by other threads when we use them, which can easily lead to thread safety issues. Therefore, we must first lock and block other threads' access before accessing shared data .

Common examples are row locks, table locks, read locks, write locks, etc. in the database


Optimistic lock

Optimistic locking is the opposite of pessimistic locking, which is more optimistic. It always believes that the probability of multiple threads modifying shared resources at the same time is low, so it doesn't matter if you change it.

Optimistic lock will directly modify the shared resource, but before updating the modification result, it will verify that there are no other threads modifying the resource during this period, if not, submit the update, if any, then abandon the operation.

Because optimistic locking is not locked in the whole process, it is also called lock-free programming , which is usually implemented by CAS operation + version number mechanism .


CASE

CAS mechanism

CAS is the abbreviation of the English word Compare And Swap, which is to compare and replace , which is its core.
Three basic operands are used in the CAS mechanism, memory address V, old expected value A, and new expected value B

When we need to modify a variable, we compare the memory address V with the old expected value. If the two are the same, replace the old expected value A with the new expected value B. If they are different, use the value in V as the old expected value and continue to repeat the above operation, that is, spin .

The following are examples of success and failure respectively.
At this time, the value stored in the memory address is 9, the old expected value of thread 1 is 9, and the new expected value is 10, that is, we need to add one to the value in the
Insert picture description here
old The expected value is the same as V, and the
Insert picture description here
modification is successful at this time by swapping B and V.

Then look at the failure of the modification

At this time, the value in V is 9, and the old expected value in thread 1 is 9, and we want to change the value in V to 10.
Insert picture description here
When we are about to modify, suddenly a thread updates the data first, and the value of V becomes 14
Insert picture description here
Since the value of A is different from V at this time, we need to get the value in V again and calculate the new expected value
Insert picture description here

At this time, the two are the same, the replacement is completed, V=15

As can be seen from the above, CAS is an optimistic lock . It optimistically believes that the concurrency in the program is not so serious, so it lets the thread keep trying to update.


ABA problem

The so-called ABA problem is that a variable is changed from A to B, then from B to become the A .

Suppose I am withdrawing money from the bank. At this time, I have 1,000 yuan in my account. I want to withdraw 500 yuan from it. However, due to sudden network fluctuations, this operation has been repeated twice, so
Insert picture description here
we can only Perform the first deduction. After the execution, A != V, so the second thread will continue to spin and compare.
Insert picture description here
At this time, it happens that the roommate repaid the 500 yuan you borrowed a few years ago, and your amount has changed again. 1000
Insert picture description here
At this time, thread 2 deducted 500 for you again, so you withdrew 500 yuan, but accidentally deducted 1000.
Insert picture description here
So how to solve this problem? We can introduce a version number mechanism , which can only be replaced
Insert picture description here
when the version number is the same. When the roommate transfers to you, due to the change in the value, the version number has also been modified.
Insert picture description here
At this time, although the values ​​in A and V are the same, But the version number is different, so it cannot be exchanged


Advantages and disadvantages of CAS

advantage

  • When there is less concurrency or less variable modification operations, the efficiency will be higher than traditional locking, because it does not involve switching between user mode and kernel mode.

Disadvantage

  • Spin is compared and replaced. When the amount of concurrency is large, it may not be successful because the variables are updated all the time, and the continuous spinning will cause excessive CPU pressure.
  • CAS can only guarantee the atomicity of a variable, but not the atomicity of the entire code block, so it is still necessary to lock when dealing with atomic updates of multiple variables.
  • The above ABA problem can be solved by introducing the version number

Mutex VS Spinlock

Mutual exclusion locks and spin locks are the bottom two types of locks. Most of the high-level locks are implemented based on them. Let’s talk about their differences.

Mutex

Mutex is a sleep lock , that is, when a thread occupies the lock, other threads that fail to lock will go to sleep

For example, we have two threads A and B competing for the mutex lock together. When thread A successfully grabs the mutex lock, the lock will be monopolized by him. Before it releases the lock, B's lock operation will fail, and At this time, thread B gives up the CPU to other threads, while itself is blocked.

The phenomenon that the mutex lock enters blocking after failing to lock is implemented by the kernel of the operating system , as shown in the figure below

Insert picture description here

  • When the lock fails, the kernel will put the thread to sleep and switch the CPU to other threads to run. Switch from user mode to kernel mode at this time
  • When the lock is released, the kernel brings the thread to the ready state, and then wakes up the thread at an appropriate time to acquire the lock and continue to execute the business. Switch from kernel mode to user mode at this time

So when the mutual exclusion lock fails to lock, it is accompanied by the overhead of two context switches , and if we lock the time for a short time, the context switch time may be longer than the lock time.

Although the difficulty of using mutexes is relatively low, considering the overhead of context switching, in some cases we will give priority to spin locks.


Spin lock

The spin lock is implemented based on CAS . It completes the lock and unlock operations in the user mode and does not actively switch context , so its overhead is less than that of the mutex lock.

Any thread that tries to acquire the lock will continue to try (ie spin) until the lock is acquired, and only one thread can acquire the spin lock at the same time.

The essence of spin lock is actually a CAS operation on an integer in memory . Locking involves the following steps

  1. Check the value of the integer, if it is 0, the lock is free, then perform the second step, if it is 1, the lock is busy, and the third step is performed
  2. Set the value of the integer to 1, the current thread enters the critical section
  3. Continue spin check (back to the first step) until the integer value is 0

It can be seen from the above that the thread that failed to acquire the spin lock will always be busy waiting , spinning until it acquires the lock resource, which also requires us to release the lock as soon as possible, otherwise it will take up a lot of CPU resources


Comparison and application scenarios

Due to the different failure strategies of spin locks and mutex locks, spin locks use a busy waiting strategy, while mutex locks use a thread switching strategy. Due to different strategies, their application scenarios are also different.

Since the spin lock does not require thread switching, it is completely implemented in user mode, and the locking overhead is low. However, due to its busy waiting strategy, it is no problem for short-term locking, but it will cause long-term locking. Large consumption of CPU resources. And because it does not sleep, it can be used in interrupt handlers.

The mutex adopts a thread switching strategy. When switching to another thread, the original thread will enter the sleep (blocking) state, so if there is a requirement for sleep, you can consider using a mutex. And because sleep does not occupy CPU resources, it has a great advantage over spin locks in long-term locking.

The specific application scenarios are shown in the following table

demand Locking method
Low overhead locking Spin lock
Short-term lock-in Spin lock
Long-term lock-in Mutex
Lock in interrupt context Spin lock
Sleep is required to hold the lock Mutex

Read-write lock

The read-write lock is used to clearly distinguish between read and write operations .

Its core lies in write exclusive, read sharing

  • The read lock is a shared lock . When no thread holds the write lock, the read lock can be held concurrently by multiple threads, which greatly improves the access efficiency of shared resources. Since the read lock only has read permissions, there is no thread safety issue.
  • The write lock is an exclusive lock (exclusive lock) . When any thread holds the write lock, the other threads will block the read lock and write lock operations.

As shown below

Read lock Write lock
Read lock compatible Not compatible
Write lock Not compatible Not compatible

Method to realize

According to different implementation methods, read-write locks are divided into reader first, writer first, and read and write fair

Reader first

The reader's first expectation is that the read lock can be held by more threads to improve the concurrency of the read threads.

In order to do this, its rules are as follows: even if a thread applies for a write lock, as long as there are readers reading the content, other reader threads are allowed to continue to apply for a read lock, and the process of applying for a write lock is blocked until When no reader thread is reading, the thread is allowed to write

The process is as follows
Insert picture description here

Writers first

And the writer first is to give priority to the writer process

Suppose that at this time, a reader thread already holds a read lock and is reading, and another write thread applies for a write lock, and the write thread is blocked. In order to ensure that the writer takes priority, the subsequent reader threads will be blocked when they acquire the read lock. When the previous reader thread releases the read lock, the writer thread performs a write operation, and other threads will be blocked until the writer thread finishes writing.

The process is as follows
Insert picture description here

Literacy fair

It can be seen from the above two rules that priority to read and write will cause the other party to starve

  • When readers are prioritized , the concurrency for the reading process is high, but if there are processes always acquiring the read lock, the write process will never be able to acquire the write lock, and the write process will starve.
  • When the writer takes priority , although it can be guaranteed that the writing process will not starve to death, if the writing process has been acquiring the write lock, the reading process will never obtain the read lock, and the reading process will starve.

Since favoring either party will cause the other party to starve to death, we can make a rule that reads and writes fairly

Implementation method: Use a queue to queue the thread that acquires the lock. Whether it is the writing thread or the reading thread, the lock is locked according to the first-in-first-out principle. This also allows the reading thread to be concurrent without starvation.


Read-write lock VS mutex lock

In terms of performance, the efficiency of read-write locks is not higher than that of mutex locks. The overhead of reading locks and locking is not less than that of mutex locks, because it needs to maintain the current number of readers in real time. When the critical area is small and lock competition is not fierce, the efficiency of mutex locks is often faster

Although read-write locks may not be as fast as mutex locks, they have good concurrency. For places with high concurrency requirements, read-write locks should be given priority.

Guess you like

Origin blog.csdn.net/qq_35423154/article/details/109259881