하나의 객체를 두 개의 스레드가 접근할 때 생기는 일에 대해 알아보자.
두 개의 귤 박스(스레드)에서 상한 귤을 골라낸다고 해보자.
for (귤 in 귤박스) { if (귤 상태 is 불량) { badCounter.increment(); } }
public class Conter { private int state = 0; public void increment() { state++; } public int get() { return state; } }
귤 박스에 있는 귤의 상태가 불량이면 state를 증가시키는데, 이 때 badConter는 두 스레드가 공유하는 객체이다. 그래야 두 박스를 합쳐서 불량인 귤의 전체 수를 구할 수 있다.
LOAD state to R1 // state의 값을 레지스터에 가져온다. R1 = R1 + 1 // 레지스터에 있는 값을 1 증가시켜서 저장한다. STORE R1 to state // 레지스터의 값을 메모리의 state에 저장한다.
우리는 스레드1에서 5개의 귤을 발견하고, 스레드2에서 2개의 귤을 발견하면 state가 7이 될것이라고 기대한다.
하지만, 한 스레드에서 증가한 state의 값이 아직 메모리에 반영 되지 않았을 때 컨텍스트 스위칭이 일어나게 되면 다른 스레드에서는 아직 증가하지 않은 state의 값을 가지고 명령을 시작하게 된다.
이렇게 언제 컨텍스트 스위칭이 일어나느냐에 따라 결과가 달라지고 기대와 다른 값이 나오게 된다.
이렇게 여러 프로세스/스레드가 동시에 공유된 자원에 접근(조작)할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황을 경쟁 조건(race condition)이라고 한다.
여러 프로세스/스레드를 동시에 실행해도 공유 데이터의 일관성을 유지하는 것
프로그래밍 언어 레벨에서의 state++라는 명령문이 실제로 CPU에서 실행될 수 있는 명령어 레벨에서 봤을 때는 단일 명령문이 아니라 3가지의 명령문의 조합이다.
❓그러면 이 3가지의 명령문을 실행하는 도중에는 컨텍스트 스위칭이 일어나지 않도록 하면 될까?
➡️ 이 방법은 싱글 코어에서는 가능하지만 멀티코어에서는 두 스레드가 동시에 실행되기 때문에 불가능하다.
✅ increment()
메서드를 한 번에 한 스레드만 실행할 수 있도록 하면 된다.
다른 스레드가 메서드를 실행하려고 해도 이미 해당 메서드를 실행중인 스레드가 있으면 메서드 호출을 끝낼 때까지 기다리게 한다.
공유 데이터의 일관성을 보장하기 위해 하나의 프로세스/스레드만 진입해서 실행 가능한 영역
do { entry section // 임계 영역에 진입하기 전에 조건을 확인하는 영역 critical section // 임계 영역 exit section remainder section } while(TRUE)
1. mutual exclusion(상호 배제)
한 번에 하나의 프로세스/스레드만 임계 영역에서 실행한다.
2. progress(진행)
만약에 임계 영역이 비어있고 임계 영역에 들어가기 원하는 프로세스/스레드가 있다면, 그 중에 하나는 임계 영역에서 실행될 수 있도록 해야한다.
3. bounded waiting(한정된 대기)
무한정 임계 영역에 들어가지 못하고 기다리게 하면 안된다.