peterson 해결 방법과 같은 소프트웨어 기반 해결책은 최신 컴퓨터 아키텍처에서 명령어 순서 재정렬로 인한 문제가 발생할 수 있다. 그렇기 때문에 하드웨어는 특수한 명령어를 통해 해결책을 지원한다.
컴퓨터 아키텍처가 응용 프로그램에게 제공하는 메모리 접근 시 보장되는 사항을 결정하는 방식을 Memory Model이라고 한다.
한 프로세서의 메모리 변경 결과가 다른 모든 프로세서에 즉시 보임
한 프로세서의 메모리 변경 결과가 다른 모든 프로세서에 즉시 보이지 않음
메모리 모델에 따라서 메모리 변경 결과를 확신 할 수 없다. 이러한 문제를 해결하기 위해 Memory Barriers (메모리 장벽) 또는 Memory fences(메모리 펜스)를 사용할 수 있다.
아래 예제는 flag값이 x값 보다 먼저 적재 되도록 보장한다.
while(!flag)
memory_barrier();
print x;
아래 예제는 x값이 flag값 보다 먼저 적재 되도록 보장한다.
x=100;
memory_barrier();
flag=true;
아래 소개하는 두 하드웨어 명령어는 원자적(atomcially)으로 실행된다. 명령어가 원자적으로 실행된다는것은 두 명령어가 동시에 실행되어도 스케쥴링에 의해 쪼개지지 않고 항상 순서대로 실행된다는 의미이다. 명령어가 실행되면 해당 명령이 끝날때까지 현재 명령어만 실행됨을 보장한다.
boolean test_and_set(boolean *target){
boolean rv = *target;
*target = true;
return rv;
}
do{
while(test_and_set(&lock));
/* critical section */
lock = false;
/* remainder section */
}while(true);
int compare_and_swap(int *value, int expected, int new_value){
int temp = *value;
if (*value == expected)
*vaule = new_value;
return temp;
}
while(true){
while(compare_and_swap(&lock, 0, 1) != 0);
/* critical seciton */
lock = 0;
/* remainder section */
}
while (true) {
waiting[i] = true;
key = 1;
while (waiting[i] && key == 1)
key = compare_and_swap(&lock, 0, 1);
waiting[i] = false;
/* critical section */
j = (i + 1) % n;
while ((j != i) && !waiting[j])
j = (j + 1) % n;
if (j == i)
lock = 0;
else
waiting[j] = false;
/* remainder section */
}
위에서 설명한 compare_and_swap()는 상호 배제를 제공하기 위해 직접 사용되지 않는다. 오히려 critical section의 문제를 해결하는 다른 도구를 구축하는데 기본 구성 요소로 사용된다. 그렇게 구현된 도구는 Atomic Variables(원자적 변수)로 해당 자료형에 대한 원자적 연산을 제공한다.
void increment(atomic_int *v){
int temp;
do{
temp = *v;
}while(temp != compare_and_swap(v, temp, temp+1));
}
원자적 연산을 지원하기 때문에 상호 배제는 보장할 수 있다. 하지만 모든 상황에서 race condition을 해결할 수 는 없다.
예를 들기위해 이 글에서 설명한 생산자-소비자 패턴의 코드를 보겠다.
우리는 count 변수를 원자적 정수로 정의를 할 수 있다. 하지만 소비자가 여러 프로세스라면 소비자에서 count 값이 바뀌자마자 count != 0
이 되어 while문을 탈출하고 데이터를 소비하게 된다. 우리는 한 소비자가 하나의 데이터를 소비 하도록 생각하지만 실제로는 한 데이터를 두 소비자가 소비하고 있는 문제가 발생할 수 있다.