경쟁 상태(Race Condition)
경쟁 상태(Race Condition)란 여러 프로세스 또는 스레드가 하나의 공유 자원에 접근할 때, 접근 순서에 따라 결과 값이 달라지는 현상을 의미합니다.
멀티스레딩 환경에서 동기화가 적절히 이루어지지 않으면, 여러 스레드가 동시에 공유 자원을 수정하려고 시도하면서 의도치 않은 결과가 발생할 수 있습니다. 이는 데이터의 불일치와 예측 불가능한 동작을 초래할 수 있어 프로그램의 안정성을 해칠 수 있습니다.
예제: 경쟁 상태 발생 코드
#include <iostream>
#include <thread>
int counter = 0;
void increment() {
for (int i = 0; i < 100000; i++) {
counter++;
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
위 코드에서는 두 개의 스레드가 counter 변수를 증가시키지만, 동기화가 없기 때문에 counter 값이 예측과 다르게 나올 수 있습니다. 실행할 때마다 다른 값이 출력될 가능성이 있으며, 이는 경쟁 상태로 인해 발생하는 문제입니다.
임계 영역(Critical Section)
임계 영역이란 여러 프로세스 또는 스레드가 공유 자원에 접근하는 코드 영역을 의미하며, 이 영역에서의 실행 순서에 따라 결과가 달라질 수 있습니다.
경쟁 상태를 방지하려면 임계 영역에서 한 번에 하나의 프로세스 또는 스레드만 실행되도록 동기화해야 합니다. 이를 위해 다음과 같은 동기화 기법이 사용됩니다.
동기화 기법
경쟁 상태를 방지하고 일관된 결과를 유지하기 위해서는 다음 세 가지 조건을 만족해야 합니다.
- 상호 배제(Mutual Exclusion):
- 하나의 프로세스가 임계 영역을 실행하는 동안 다른 프로세스는 해당 영역에 접근할 수 없어야 합니다.
- 진행(Progress):
- 임계 영역을 실행 중인 프로세스가 없을 때, 접근을 요청한 프로세스 중 일부가 반드시 실행될 수 있어야 합니다.
- 한정된 대기(Bounded Waiting):
- 임계 영역에 접근하려는 프로세스가 무한정 기다리지 않도록 보장해야 합니다.
뮤텍스(Mutex)
뮤텍스(Mutex, Mutual Exclusion)는 하나의 프로세스 또는 스레드만이 임계 영역에 접근할 수 있도록 하는 동기화 기법입니다.
- 뮤텍스는 락(Lock)을 기반으로 동작하며, 한 스레드가 임계 영역을 실행하는 동안 다른 스레드는 대기해야 합니다.
- 특정 스레드가 락을 획득하면, 다른 스레드는 락이 해제될 때까지 대기합니다.
- 락을 획득한 스레드가 반드시 해제를 수행해야 하며, 그렇지 않으면 데드락(Deadlock)이 발생할 수 있습니다.
뮤텍스 사용 예제 (C++)
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; i++) {
mtx.lock();
counter++;
mtx.unlock();
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Counter: " << counter << std::endl;
return 0;
}
위 코드에서는 std::mutex를 사용하여 counter 변수에 대한 동시 접근을 방지합니다.
스핀락(Spinlock)
스핀락은 프로세스 또는 스레드가 락을 획득할 때까지 반복적으로 확인하는 방식입니다.
- 일반적인 락과 달리 대기하는 동안 문맥 전환(Context Switching)이 발생하지 않습니다.
- 대신, CPU를 점유한 상태에서 락이 해제될 때까지 바쁜 대기(Busy Waiting)를 수행합니다.
- 짧은 시간 동안의 동기화가 필요할 때는 효과적이지만, 장기적으로는 CPU 자원을 낭비할 수 있습니다.
#include <atomic>
std::atomic_flag lock = ATOMIC_FLAG_INIT;
void spinlock_lock() {
while (lock.test_and_set(std::memory_order_acquire));
}
void spinlock_unlock() {
lock.clear(std::memory_order_release);
}
위 코드는 std::atomic_flag를 사용하여 스핀락을 구현한 예제입니다.
세마포어(Semaphore)
세마포어는 공유 자원에 동시에 접근할 수 있는 프로세스 또는 스레드의 수를 제한하는 기법입니다.
- 세마포어 값이 1이면 뮤텍스와 유사한 동작을 하지만, 1보다 크면 여러 스레드가 동시 접근할 수 있습니다.
- 프로세스가 세마포어를 획득하면 값이 감소하고, 해제하면 값이 증가합니다.
세마포어 사용 예제 (C++)
#include <iostream>
#include <thread>
#include <semaphore>
std::counting_semaphore<3> sem(3);
void task(int id) {
sem.acquire(); // 세마포어 획득
std::cout << "Thread " << id << " is in critical section.\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
sem.release(); // 세마포어 해제
}
int main() {
std::thread t1(task, 1);
std::thread t2(task, 2);
std::thread t3(task, 3);
std::thread t4(task, 4);
t1.join();
t2.join();
t3.join();
t4.join();
return 0;
}
위 코드에서 std::counting_semaphore를 사용하여 최대 3개의 스레드가 임계 영역에 접근할 수 있도록 설정하였습니다.
결론
경쟁 상태를 방지하기 위해서는 뮤텍스, 스핀락, 세마포어 등의 동기화 기법을 적절히 사용해야 합니다.
- 뮤텍스: 하나의 프로세스만 접근 가능
- 스핀락: 락이 해제될 때까지 반복 확인
- 세마포어: 지정된 수의 프로세스가 동시에 접근 가능
해당 글은 다음 도서의 내용을 참고하고 추가적인 정보를 기재한 글임을 밝힙니다.
이수진, ⌜기술면접대비 CS전공 핵심요약집⌟, (주)도서출판 길벗
'CS지식 > 운영체제' 카테고리의 다른 글
IPC(Inter Process Communication), 좀비 프로세스, 고아 프로세스 (0) | 2025.02.04 |
---|---|
교착 상태(Deadlock)와 스레드 안전(Thread Safe) (0) | 2025.02.03 |
동시성과 병렬성, 멀티 프로세스와 멀티 스레드 (0) | 2025.01.31 |
PCB(Process Control Block), 프로세스의 생성, 프로세스 상태도 (0) | 2025.01.30 |
프로세스, 메모리 구조 (0) | 2024.11.05 |