개요
공학 분야에서 경쟁 상태(race condition)란 둘 이상의 입력 또는 조작의 타이밍이나 순서 등이 결과값에 영향을 줄 수 있는 상태를 말한다. 입력 변화의 타이밍이나 순서가 예상과 다르게 작동하면 정상적인 결과가 나오지 않게 될 위험이 있는데 이를 경쟁 위험이라고 한다. 스레드에서도 어떤 스레드가 먼저 실행될지 모르기 때문에, 스레드가 서로 공유하는 자료에 접근하여 자료의 값을 변경할경우 예측할 수 없는 값이 나올 수 있다. 이러한 상태를 경쟁 상태에 놓여 있다고 한다.
보통 2가지로 나눌 수 있다.
- Atomicity violation: Atomic 하다고 알려져야 만 하는 변수가 Atomic하지 않아서 접근에 따라서 달라지는 문제가 생기는 것이다. 예를 들어서 T1과 T2가 같은 변수를 읽어야 한다고 해보자. 이 경우 변수가 Atomic하지 않으면 서로 다른 값으로 변수에 접근 할 수 있다. 이 문제는 LOCK을 이용하여 해결할 수 있다.
- Order Violation: Variable의 접근 순서가 달라서 문제가 생기는 것이다. 한 예는 Use after free이다. 만약 T1이 할당한 변수를 T2가 해제 한다고 해보자. 만약 T2가, T1이 끝나기도 전에 스케쥴링 되면 Order Violation이 생겨서 Use after free문제가 생긴다. 이 문제는 Condvar을 이용하여 해결 할 수 있다.
해결법
Race condition을 없애는 방법은 크게 두가지가 있다. Safety와 Liveness가 그것이다. 전자는 프로그램이 절대로 경쟁 상태에 도달하지 않도록 보장하는 것이고 후자는 프로그램이 경쟁 상태에 들어가더라도 어느 시점 이후에는 경쟁 상태에서 해제됨을 구현하는 것이다.
Safety를 구현하는 방법은 크게 다음과 같다.
- Synchronization: 스레드가 서로 동기화 되게 하여 서로의 상태를 추적하여 경쟁 상태에 들어가지 않도록 하는 것이다.
- Mutual Exclusion: 한순간에는 한 스레드만 명령을 수행하는 것
- Critical Section: Mutal Exclusion이 일어나는 부분
- Lock: 락을 사용하여 Mutual Exclusion을 구현함.
예시
class Counter implements Runnable{
private int c = 0;
public void increment() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
c++;
}
public void decrement() {
c--;
}
public int getValue() {
return c;
}
@Override
public void run() {
//incrementing
this.increment();
System.out.println("Value for Thread After increment "
+ Thread.currentThread().getName() + " " + this.getValue());
//decrementing
this.decrement();
System.out.println("Value for Thread at last "
+ Thread.currentThread().getName() + " " + this.getValue());
}
}
public class RaceConditionDemo{
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(counter, "Thread-1");
Thread t2 = new Thread(counter, "Thread-2");
Thread t3 = new Thread(counter, "Thread-3");
t1.start();
t2.start();
t3.start();
}
}