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

1Read counter value0
2Read counter value0
30 + 1 calculation0
40 + 1 calculation0
51 save1
61 save1

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 SectionAreas where permissions are obtained before entering the Critical Section
Critical SectionAreas that access shared resources
Exit SectionCritical Section Area where privileges are returned after use
Remainder SectionAny 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 rangeOnly code that accesses shared resources should be placed in the Critical Section.
short run timeDo not perform lengthy tasks while holding the lock.
Consistent lock orderWhen using multiple locks, they are always acquired in the same order.
Unlock guaranteeWrite so that the lock is released even if an error occurs.
Select appropriate synchronization objectDepending on the situation, Mutex, Spinlock, Semaphore, and Atomic Operations are used separately.
Shared Resource Baseline DesignDesign 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

DeadlockIf the order of lock acquisition is messed up, it may stop while waiting for each other.
StarvationCertain threads may continue to fail to obtain the lock
Priority InversionA low priority thread may be holding the lock, allowing a high priority thread to wait.
Lock ContentionPerformance deteriorates when multiple threads frequently request the same lock.
Over-serializationIf too large a range is bound with a lock, parallelism is reduced.
Missing UnlockIf 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.