Critical Section
Critical Section: An area where data inconsistencies may occur if multiple processes or threads execute simultaneously during sections of code that access shared resources.
Protecting Critical Sections
A race condition may occur when multiple execution flows access the same variables, files, data structures, device states, etc. at the same time.
At this time, synchronization techniques such as Lock, Mutex, Semaphore, and Spinlock must be placed before and after the Critical Section to allow only one execution flow to access the shared resource at a time.
int counter = 0;
counter++;
The above C language code looks like a code that simply increases the counter value by 1, but in reality, it can be divided into several steps: reading the value from memory, incrementing the value in the register, and storing it back in memory. Operations such as globalvar++ are also divided into several instructions in the form of load, add, and store at the assembly level, and if multiple threads alternately execute between these instructions, a race condition may occur.
It roughly takes the following form:
mov eax, DWORD PTR [counter]
add eax, 1
mov DWORD PTR [counter], eax
In other words, counter++ is not an atomic operation.
If two threads execute counter++ at the same time, the following situations may occur:
Order Thread A Thread B counter
| 1 | Read counter value | 0 | |
| 2 | Read counter value | 0 | |
| 3 | 0 + 1 calculation | 0 | |
| 4 | 0 + 1 calculation | 0 | |
| 5 | 1 save | 1 | |
| 6 | 1 save | 1 |
Originally, the result should be 2 because both threads each incremented by 1.
However, because both threads read the same existing value of 0 and each stored 1, the final result is 1.
This problem is Race Condition.
Critical Section Structure
Critical Section is generally expressed in the following structure.
do {
entry section
critical section
exit section
remainder section
} while (1);
The meaning of each area is as follows.
Area Description
| Entry Section | Areas where permissions are obtained before entering the Critical Section |
| Critical Section | Areas that access shared resources |
| Exit Section | Critical Section Area where privileges are returned after use |
| Remainder Section | Any remaining areas of code that are not directly related to shared resources. |
Precautions when using Critical Section
Critical Section should be kept as short as possible.
If you perform a task that takes a long time within the Critical Section, all other threads will wait.
In particular, performing tasks such as file I/O, network I/O, long operations, and waiting for user input while holding the lock can significantly reduce overall performance.
pthread_mutex_lock(&lock);
update_shared_state();
pthread_mutex_unlock(&lock);
do_slow_work();
As shown above, it is best to place only the part that updates the shared state in the Critical Section and perform long-consuming tasks outside the lock.
Conversely, code like the following is not good:
pthread_mutex_lock(&lock);
update_shared_state();
do_slow_work();
pthread_mutex_unlock(&lock);
If do_slow_work() takes a long time, all other threads will wait.
Critical Section Design Principles
When designing a critical section, the following points should be considered.
Principle Description
| minimum range | Only code that accesses shared resources should be placed in the Critical Section. |
| short run time | Do not perform lengthy tasks while holding the lock. |
| Consistent lock order | When using multiple locks, they are always acquired in the same order. |
| Unlock guarantee | Write so that the lock is released even if an error occurs. |
| Select appropriate synchronization object | Depending on the situation, Mutex, Spinlock, Semaphore, and Atomic Operations are used separately. |
| Shared Resource Baseline Design | Design locks based on the data that needs to be protected, not based on code location. |
Critical Section is not simply a matter of attaching a lock to the code.
It is necessary to design together what data to protect, how much to protect, how long the lock will be held, and the order between multiple locks.
Limitations of Critical Section
Using Critical Section does not automatically solve all concurrency problems.
Limitations Description
| Deadlock | If the order of lock acquisition is messed up, it may stop while waiting for each other. |
| Starvation | Certain threads may continue to fail to obtain the lock |
| Priority Inversion | A low priority thread may be holding the lock, allowing a high priority thread to wait. |
| Lock Contention | Performance deteriorates when multiple threads frequently request the same lock. |
| Over-serialization | If too large a range is bound with a lock, parallelism is reduced. |
| Missing Unlock | If you do not unlock in an exception or error path, the entire thing may stop. |
In other words, Critical Section is a key concept to prevent race conditions, but if used incorrectly, it can create performance problems or deadlock.