여러 프로세스 or 스레드가 동시에 같은 데이터를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황
Ex. 1번스레드가 0이 들어있는 A라는 데이터를 10 증가하려는 상황
- 1번 스레드가 A라는 데이터를 10 증가(A는 10)
- ❗동시에 2번 스레드가 접근해서 A라는 데이터를 다시 10 증가(A는 20)
- 1번 스레드가 A라는 데이터를 확인해보니 원했던 10이 아닌 20이 되어있는 문제
=> 이러한 문제를 해결하기 위해 공유자원에 동시에 접근하지 못하도록 접근 순서를 제어하는 방법(동기화)가 필요
이 상호배제를 동기화 기법으로 구현한 것이 세마포어와 뮤텍스이다.
현재 자원을 소유하고 있는 A가 작업을 마치고 unlock()을 호출
이 unlock() 내부에서 대기 큐를 확인해서 대기중인 B를 깨우는 명령이 존재
이후, B는 스케줄러에 의해서 CPU를 할당받아서 Mutex를 할당 받고 실행
대기하는 방법을 의미하고, 2가지 대기 방법이 존재
- Busy-Wait
- 원하는 자원을 얻기위해서 CPU를 사용하며 특정 조건을 만족할 때 까지 계속해서 기다림
- Spin Lock 방식이 존재
- Non-Busy-Wait
- 대기 큐에서 CPU 자원을 내려놓고 대기하는 방식
또 하나의 동기화 기법, 자원을 얻을 때까지(lock이 풀릴 때까지) 계속 루프를 돌면서 CPU를 점유한 채 대기 하는 방식의 Lock
=> 때문에, 만약 단일 코어의 환경에서 Spin Lock을 사용하게 되면 정작 자원을 얻고 실행해야하는 프로세스/스레드가 CPU를 할당받지 못해서 무한정 대기하는 교착상태같은 상황이 발생
여러개의 프로세스/스레드가 공유 자원에 동시에 접근하는 것을 제한하기 위한 변수
자원을 할당하는 V() 메서드는 Semaphore 변수값을 1 증가시키고, 만약 대기 중인 프로세스가 있다면, 그 중 하나를 꺠워서 실행시킨다 라는 연산으로 이루어져있음
때문에, 자원을 할당 받아서 들어가는 P() 연산과는 다르게 동작함
대기 큐에서 할당받는 우선순위에 따라서 종류가 달라지기도 함
- 강성 세마포어 : FIFO 방식
- 약성 세마포어 : 순서가 정해지지 않고 준비된 프로세스/스레드 부터 할당
Mutex
때문에, 이진 세마포어와 뮤텍스는 비슷하지만 다르다.
하지만, 속도가 느리고 구현이 복잡할 뿐아니라, HW 상에서 상호배제가 깨지는 가능성이 존재.
때문에, 원자성이 지켜지는 Mutex와 Semaphore로 해결
| 항목 | 데커의 알고리즘 | 피터슨의 알고리즘 |
|---|---|---|
| ✅ 개발자 | 데커 (Dekker) | 피터슨 (Peterson) |
| ✅ 대상 | 2개 프로세스 | 2개 프로세스 |
| ✅ 주요 변수 | flag[2], turn | flag[2], turn |
| ✅ 방식 | 상호 배제 + 번갈아 접근 | 상호 배제 + 번갈아 접근 |
| ✅ 핵심 원리 | 둘 다 진입 의사 → turn에 따라서 양보 | 진입의사를 표시하고 상대방에게 turn 양보 (먼저 양보한 프로세스가 우선) |
| ✅ 특징 | 최초의 소프트웨어 동기화 | 간결하고 명확 |
| ✅ Sleep 여부 | ❌ 없음 (CPU 계속 사용) | ❌ 없음 |
| ✅ 스케줄러 개입 | 없음 | 없음 |
flag : 진입 의사를 나타내는 변수
turn : 자원을 점유할 프로세스를 의미
추가적인 동기화 기법인 모니터가 존재한다
- 동기화를 추상화한 고수준(프로그래밍 언어 차원)의 동기화 기법
모니터는 개발자가 직접 락을 걸고 해제하지 않아도, 언어 차원에서 자동으로 동기화를 관리해주는 구조
'공유 자원'과 그 접근을 안전하게 관리하는 코드(동기화된 메서드)를 하나의 단위로 묶은 구조
언어나 시스템이 내부적으로 락을 자동 관리하므로 사용자는 더 안전하고 직관적으로 동기화할 수 있음
Mutex나 Semaphore보다 더 구조화되고 안전하게 Critical Section을 보호할 수 있음
Ex. Java의 Synchronized
class BankAccount {
private int balance = 0;
public synchronized void deposit(int amount) {
balance += amount;
}
public synchronized int getBalance() {
return balance;
}
}
스레드가 모니터 안의 함수(메서드)를 호출하면
모니터는 자동으로 lock을 걸어줌 → 임계 영역 보호
함수 실행이 끝나면 → lock이 자동으로 해제됨
조건 변수를 이용해 상황에 따라 대기(wait)하거나 깨우기(notify) 가능