⚠️ 해당 포스팅은 인프런 공룡책 강의를 듣고 개인적으로 정리하는 글입니다. 정확하지 않은 정보가 있을 수 있으니 주의를 요합니다.
Chapter 6 CPU Synchronization Tools
int count = 5;
while(true) {
while(count == BUFFER_SIZE)
; /* do nothing */
buffer[in] = next_produced;
in = (in + 1) % BUFFER_SIZE;
count++;
}
while(ture) {
while (count == 0)
; /* do nothing */
next_consumed = buffer[out];
out = (out + 1) % BUFFER_SIZE;
count --;
/* consume the item in next_consumed */
}
/* 결과가 6이 나오는 것이 아니라, 4,5도 함께 나오며 제대로된 결과가 출력되지 않음 */
count
의 값이 5라고 지정하고 생산자와 소비자가 각각 카운트를 증가시키고 감소시키는 작업을 동시에 수행했을 때 결과가 4나 5, 혹은 6이 나오며 우리가 기대한 결과를 받지 못하게 된다.count++
과 count--
는 우리가 작성한 코드에서 보았을 때는 한 줄의 간단한 코드이다. /* count ++ */
register1 = count
register1 = register1 + 1
count = register1
/* count -- */
register2 = count
register2 = register2 - 1
count = register2
count++
과 count--
명령은 로우 레벨 단계에서 임의의 순서로 끼어지게 되며, 순차 실행과 동일한 결과를 낳게 된다.
public class RaceCondition2 {
public static void main(String[] args) throws InterruptedException {
RunnableTwo run1 = new RunnableTwo();
RunnableTwo run2 = new RunnableTwo();
Thread t1 = new Thread(run1);
Thread t2 = new Thread(run2);
t1.start();t2.start();
t1.join(); t2.join();
System.out.println("Result : " + RunnableTwo.count);
}
}
public class RunnableTwo implements Runnable{
static int count = 0; // 공유 데이터
// 동시에 접근하며 경쟁 상황(Race condition) 발생
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
/* 결과가 20000이 아니라, 무작위로 나옴. */
데커의 알고리즘 : 두 프로세스 간 문제를 해결하는 방법
turn
과 flag
변수로 크리티컬 섹션에 진입할 프로세스 혹은 스레드를 결정한다.public class PetersonTest {
static int count = 0; // 공유 자원
static int turn = 0; // 다음 차례 지정
static boolean[] flag = new boolean[2]; // 실행 스레드 지정
public static void main(String[] args) {
Thread pThread = new Thread(new Producer());
Thread cThread = new Thread(new Consumer());
pThread.start();
cThread.start();
try {
pThread.join();
cThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("남은 갯수 : " + count); // 공공재 총량 출력
}
static class Producer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
flag[0] = true; // 나 이제 실행할 준비가 됐어.
turn = 1; // 내가 실행하면, 다음 차례는 너야
while (flag[1] && turn == 1) ; // Busy Waiting
// 너가 이미 실행을 하고 있으면 난 기다릴게
// 실행을 다 마쳤으니 이제 내 차례고, 난 이제 크리티컬 섹션에 진입할거야.
count++; // 크리티컬 섹션 조작
flag[0] = false; // 내 실행을 모두 마쳤어.
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
flag[1] = true; // 나 이제 실행할 준비가 됐어.
turn = 0; // 내가 실행하면, 다음 차례는 너야.
while (flag[0] && turn == 0) ; // Busy Waiting
// 너가 실행중이면, 난 기다릴게.
// 실행을 다 마쳤으니 이제 내 차례고, 난 이제 크리티컬 섹션에 진입할거야.
count--; // 크리티컬 섹션 조작
flag[1] = false; // 내 실행을 모두 마쳤어.
}
}
}
}
busy waiting
)를 돌게 되는데, 무한 루프에도 CPU 자원을 사용하기 때문에 CPU를 효율적으로 사용하지 못하는 문제가 있다.이게 무슨소리야? -> a++ 은 아까 보았듯이 기계어 세 줄로 이루어져 있는데, 이를 기계어 한 줄로 줄여 중간에 인터럽트 되는 상황을 막겠다는 이야기이다.
test_and_set()
명령어와 compare_and_swap()
명령어로 예를 들어 설명해본다. boolean test_and_set(boolean *target) {
boolean rv = *target;
*target = true;
return tv;
}
do {
while (test_and_set(&lock)); /* do nothing, context switching X */
// 본인의 lock 을 true 로 만들고 진입한다.
/* critical section */
lock = false; // 크리티컬 섹션을 빠져나온 후 false로 만들어 다른 프로세스 진입을 허용
/* remainer section */
} while (true);
int compare_and_swap(int *value, int expected, int new_value) {
int temp = *value;
if (*value == expected)
*value = new_value;
return tmp;
}
while (true) {
while (compare_and_swap(&lock, 0, 1) != 0);
/* do nothing */
/* critical section */
lock = 0;
/* remainer section */
}
import java.util.concurrent.atomic.AtomicBoolean;
public class PetersonTest2 {
static int count = 0;
static int turn = 0;
static AtomicBoolean[] flag; // 원자 변수
static {
flag = new AtomicBoolean[2];
for (int i = 0; i < flag.length; i++)
flag[i] = new AtomicBoolean();
}
public static void main(String[] args) {
Thread pThread = new Thread(new PetersonTest2.Producer());
Thread cThread = new Thread(new PetersonTest2.Consumer());
pThread.start();
cThread.start();
try {
pThread.join();
cThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("남은 갯수 : " + count); // 공공재 총량 출력
}
static class Producer implements Runnable {
@Override
public void run() {
for (int k = 0; k < 100000; 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 < 100000; 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 */
}
}
}
}