while (true) {
/* produce an item in next produced */
while (counter == BUFFER_SIZE);
// while 문 내 활동 없음. 버퍼가 가득 차면 여기 갇힌다.
buffer[in] = next_produced;
in = (in + 1) % BUFFER_SIZE;
counter++:
}
while (true) {
while (counter == 0);
// counter가 비면 소비할 게 없으므로 여기 갇힌다.
next_consumed = buffer[out];
out = (out + 1) & BUFFER_SIZE;
counter--;
/* consume the item in next consumed */
}
counter++
의 실제 구현 register1 = counter
register1 = register1 + 1
counter = register1
counter--
의 실제 구현 register2 = counter
register2 = register2 - 1
counter = register2
두 함수가 동시에 실행되면 코드가 한 줄씩 각각 수행되며, context switch가 코드 한 줄마다 일어날 수 있다.
이 경우, 사용자가 원하는 결과와 다르게 나타날 수 있다. -> 데이터 불일치 문제
register1 = counter
(register1 = 5)register1 = register1 + 1
(register1 = 6)register2 = counter
(register2 = 5)register2 = register2 - 1
(register2 = 4)counter = register1
(counter = 6)counter = register2
(counter = 4)interleaving: 어떤 명령의 흐름 속에 다른 명령, 혹은 프로세스가 섞여서 수행되는 현상
critical section은 하나의 프로세스만 독점해서 사용할 수 있으므로, 최대한 짧고 간결하게 작성하는 것이 좋다.
💡 Progress vs. Bounded Waiting
- progress: 아무도 없으면 누군가는 임계영역에 들어가야 한다. → deadlock 해결 조건
- bounded waiting: 요청하면 제한시간 내에 임계영역으로 들어가야 한다. → starvation 해결 조건
→ progress를 만족해도 bounded waiting을 만족하지 못할 수 있다.
next_available_pid
를 호출한다. 이는 다음에 누군가가 fork()
하면 그때 부여할 pid를 미리 저장하고 있는 공유자원이다.fork()
하고, 동시에 next_available_pid
에 접근하여 pid가 같은 두 자식 프로세스가 생기는 문제가 발생한다.int turn
: 누가 임계영역에 들어갈 것인지 나타냄bool flag[2]
: true이면 critical에 들어가겠다는 request, 실행 중임을 나타낸다.Process Pi와 Pj가 존재할 때, Pi의 알고리즘
do {
/* entry section */
flag[i] = true;
turn = j;
while (flag[j] && turn == j); << j의 차례일 경우 루프 내에서 대기
/* critical section */
/* exit section */
flag[i] = false;
/* remainder section */
} while (true);
Peterson's Solution은 3가지 조건을 증명할 수 있다.
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 */
counter++;
/* 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 */
counter--;
/* exit section */
flag[1] = false;
/* remainder section */
}
pthread_exit(0);
}
Peterson's Solution은 현대 컴퓨터 구조에서 완벽한 작동이 보장되지 않는다.
간단한 해결 방법이다.
disable_interrupt()
호출 -> 인터럽트를 막고 CPU의 점유 보장enable_interrupt()
호출그러나, 이 방법은 과한 방법이다.
bool test_and_set (bool *target) {
bool rv = *target;
*target = true;
return rv;
}
// return 값: target의 상태. return 값과 관계없이 이 함수 호출 후 target의 값은 1
do {
// entry section
while (test_and_set(&lock)) ; // do nothing
/* critical section */
// exit section
lock = false; // critical setcion을 마치고 false로 바꾸기 전까지 다음에 들어온 것들은
// 무한루프에 빠져 있다.
/* remiander section */
} while (true);
do {
waiting[i] = true;
while(waiting[i] && test_and_set(&lock)); // 처음 진입하면 바로 while 문 통과
waiting[i] = false;
/* critical section */
j = (i+1) % n; // 현재 번호 뒤부터 탐색한다.
while ((j != i) && !waiting[j]) // j가 i랑 같지 않고, j는 기다리고 있지 않으면
j = (j+1) % n; // 다음 칸을 본다.
if (j == i) lock = 0; // 한바퀴 다 돈 상태 -> lock을 초기화하여 들어오는 대로
// 위의 while문 바로 통과 가능한 상태
else waiting[j] = false; // j만 한정하여 while 문 통과 가능
/* remainder section */
} while (true);