Dekker’s Algorithm 2개의 프로세스가 공유 자원을 문제 없이 사용할 수 있는 알고리즘Eisenberg & McGuire’s Algorithm: n개의 프로세스를 사용하여 n - 1 이하의 대기 시간을 보장하는 알고리즘Peterson’s Algorithm Critical Section Problem 가장 완전하게 해결한 알고리즘. But, load & store와 같은 기계어 명령이 현대 컴퓨터에서 제대로 작동할 것이라는 보장이 안된다.while (true)
{
flag[i] = true;
turn = j;
while (flag[j] && turn == j) // entry section
;
/* critical section */
flag[i] = false; // exit section
/* remainder section */
}
i, j 두 개의 프로세스 존재
i 프로세스는 j 프로세스가 exit section에 도달해 flag[j] = false; 가 되면 Critical section 진입j 프로세스가 remainder section 종료 후 다시 entry section으로 돌아와서 turn = i; 가 되면 Critical section 진입→ i, j 두 개의 프로세스가 동시에 Critical section에 진입 불가능
간단하게 구현해보자
#include <stdio.h>
#include <pthread.h>
#define true 1
#define false 0
int sum = 0;
int turn;
int flag[2];
int main()
{
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, producer, NULL);
pthread_create(&tid2, NULL, consumer, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
printf("sum = %d\n", sum);
}
//<생산자 코드>
void* producer(void* param)
{
for (int k = 0; k < 10000; ++k)
{
/* entry section */
flag[0] = true;
turn = 1;
while (flag[1] && turn == 1)
;
/* critical section */
sum++;
/* exit section */
flag[0] = false;
/* remainder section */
}
pthread_exit(0);
}
//<소비자 코드>
void* consumer(void* param)
{
for (int k = 0; k < 10000; ++k)
{
/* entry section */
flag[1] = true;
turn = 0;
while (flag[0] && turn == 0)
;
/* critical section */
sum--;
/* exit section */
flag[1] = false;
/* remainder section */
}
pthread_exit(0);
}
코드를 실행해보면 기대값 (0)이 아닌 다른 값들이 종종 나온다.
피터슨 솔루션은 기계어 명령의 생성 방식에 따라 제대로 동작하지 않을 수 있다.
→ entry section에서 Context Switch가 발생하기 때문
Note:
i 프로세스가 Critical section 실행 중일 때 j 프로세스는 대기i 프로세스가 Critical section 완료 시 j 프로세스가 진입한다.Test and modify
a++ 동작을 Load, Add, Store의 3 클럭으로 나누는 대신 한번에 동작하도록 한다.
Test and swap
두 개의 변수를 swap 할 때 tmp = i, i = j, j = tmp의 3 클럭으로 나누는 대신 한번에 동작하도록 한다.
-> 운영체제에서는 test_and_set(), compare_and_swap()의 형태로 제공한다. 해당 명령 수행 중에는 절대로 Context Switch가 일어나지 않는다.
compare_and_swap() 하드웨어 명령을 사용하여 구현 가능 원자적 변수의 사용을 통해 하나의 변수(single variable)에서 발생하는 경쟁 상태(race condition)를 막고 상호 배제(mutual exclution)를 보장할 수 있음import java.util.concurrent.atomic.AtomicBoolean;
public class Peterson2
{
static int count = 0;
static int turn = 0;
static AtomicBoolean[] flag;
//flag를 원자적 변수로 설정
static
{
flag = new AtomicBoolean[2];
for (int i = 0; i < flag.length; i++)
flag[i] = new AtomicBoolean();
}
static class Producer implements Runnable
{
@Override
public void run()
{
for (int k = 0; k < 10000; k++)
{
/* entry section */
flag[0].set(true); // 값을 직접 대입하는 대신 메소드를 사용함.
turn = 1;
while (flag[1].get() && turn == 1)
;
/* critical section */
count++;
/* exit section */
flag[0].set(false);
/* remainder section */
}
}
}
static class Consumer implements Runnable
{
@Override
public void run()
{
for (int k = 0; k < 10000; k++)
{
/* entry section */
flag[1].set(true);
turn = 0;
while (flag[0].get() && turn == 0)
;
/* critical section */
count--;
/* exit section */
flag[1].set(false);
/* remainder section */
}
}
}
public static void main(String[] args) throws Exception
{
Thread t1 = new Thread(new Producer());
Thread t2 = new Thread(new Consumer());
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(Peterson1.count);
}
}
참고 :
Silberschatz et al. 『Operating System Concepts』. WILEY, 2020.