경쟁 상태(Race Condtion) 이란?
경쟁 상태는 두 개 이상의 스레드 또는 프로세스가 공유 자원에 동시에 접근하고 수정을 시도할 때 발생 할 수 있는 문제이다.
즉 병행 프로그래밍 또는 동시성 프로그래밍 환경에서 발생하는 문제로써, 스레드 간의 실행 순서가 예상과 다를 경우 의도와 다른 결과가 발생하며 프로그램의 정확성과 안정성을 저해하는 결과 유발한다.
경쟁 상태가 발생하는 기본적인 원인은 다음과 같다.
1. 동시성 접근 : 여러 스레드 또는 프로세스가 동시에 공유자원(변수, 파일, 데이터베이스)에 접근하려 할 때 발생한다.
2. 비원자성(Non-atomic) 연산 : 공유 자원을 수정하는 연산이 원자적이지 않을 경우, 즉 여러 단계로 나눠서 수행되는 경우 발생할 수 있다.
*원자적 : 수행 도중 중단될 수 없는 하나의 동작 단위
아래 코드에서 두 스레드는 같은 변수의 값을 증가 시키고 있다.
int sharedNumber = 0;
// 스레드 1
sharedNumber += 1;
// 스레드 2
sharedNumber += 1;
위 예시에서 스레드 1과 스레드 2는 동시에 shardNumber를 읽고 수정하려고 시도할 수 있다.
스레드 1, 스레드 2 모두 0으로 초기화된 sharedNumber에 1을 초기화 하려 하지만, 선행된 스레드의 작업으로 인해(순서가 보장되지 않아) 의도치 않은 결과 값이 나올 수 있다.
경쟁 상태가 발생하는 경우
경쟁 상태가 발생하는 몇가지의 예시
1. 계좌 잔액 수정
여러 스레드가 동시에 같은 계좌의 잔액을 수정하려고 할 때 경쟁 상태 발생 가능. 예를 들어 스레드 A가 100원을 인출하려고 하고, 동시에 스레드 B가 동시에 50원을 입금하려고 할때, 잔액을 수정하는 순서에 따라 잘못된 결과가 발생할 수 있음
2. 캐시 데이터 갱신
여러 스레드가 동시에 캐시 데이터를 갱신하려고 할 때 경쟁 상태가 발생할 수 있음. 예를 들어, 여러 스레드가 동시에 동일한 캐시 항목을 업데이트하면 캐시의 일관성이 깨질 수 있음.
3. 변수 카운터 변경
여러 스레드가 동시에 하나의 변수 카운터의 값을 증가 시키려고 할 때 경쟁 상태가 발생할 수 있음. 예를 들어, 스레드 A와 스레드 B가 동시에 카운터 값을 읽고 증가 시킬때, 두 스레드가 동시에 읽은 값에 1을 더하고 저장하게 되면 카운터가 예상치 못하게 증가할 수 있다.
경쟁 상태를 피하는 방법
경쟁 상태를 피하기 위해선 다음과 같은 방법을 사용할 수 있다.
1. 락(lock) 사용 : 공유 자원에 접근할 때 락 메커니즘을 사용하여 스레드 간에 동기화를 유지하고 경쟁 상태를 방지 (Ex. DB Lock)
2. 원자적 연산 사용 : 원자적 연산을 사용하여 공유 자원을 수정하는 동작을 원자적으로 수행하도록 한다.
ex. 트랜잭션을 이용하여 하나의 작업단위에 다른 작업이 영향을 미치지 않고 독립적으로 수행되도록 처리
3. Thread Safe 자료구조 사용 : ConcurrentHashMap, CopyOnWriteArrayList 같은 다중 스레드 환경에서도 안전하게 데이터를 읽고 쓸 수 있는 자료구조를 사용한다. 이런 자료구조는 다중 스레드 환경에서 일관성과 안정성을 보장한다.
4. 동기화 기술 사용 : 예로 Java의 'synchronized' 키워드나 'java.util.concurrent' 패키지의 동기화 클래스 및 메서드를 활용하여 동시성 문제를 다른다.
교착 상태(Dead Lock) 이란?
교착 상태란 두 개 이상의 프로세스 또는 스레드가 서로가 가진 자원을 얻기 위해 무한히 기다리는 상태를 의미한다.
일반적으로 교착상태는 네 가지 조건이 동시에 충족될 때 발생한다.
1. 상호 배제 (Mutual Exclusion) : 자원은 동시에 하나의 프로세스 또는 스레드에 의해 사용될 수 있어야 한다. (자원의 동시 접근을 허용하지 않는다.)
2. 점유 대기 (Hold and Wait) : 프로세스가 이미 어떤 자원을 가지고 있으면서 다른 자원을 기다린다.
3. 비선점 (No Preemption) : 자원은 다른 프로세스에 의해 강제로 뺏길 수 없어야 한다.
4. 순환 대기(Circular Wait) : 프로세스의 그룹에서 순환 대기 관계가 형성되어야 한다. 즉 각 프로세스는 다음 프로세스가 점유한 자원을 대기하고 있어야 한다.
교착 상태를 피하는 방법
실무에서 교착상태 이슈는 생각보다 빈번히 일어나며, 교착 상태가 발생하면 프로그램이 무한히 멈춰버리며, 장애상황으로 이어지기때문에 선제적인 예방이 중요하다.
1. 자원 해제 - 프로세스가 필요한 모든 자원을 한 번에 요청하도록 변경하고, 모든 자원을 점유한 후에 요청을 해제하도록 하는 방법을 사용한다.
2. 타임아웃과 롤백 - 일정 시간 동안 자원을 얻지 못하면 프로세스가 해당 자원을 해제하고 다른 작업을 수행하도록 하는 방법을 사용한다.
ex. 일정시간 이상 자원을 얻지 못할 경우 기존 작업된 내용을 모두 롤백하여 요청을 무효화 시킨다.
3. 자원 할당 그래프 - 교착 상태를 감지하기 위한 그래프 모델을 사용하여 프로세스와 자원 간의 관계를 모니터링한다.
보통 교착 상태는 운영 체제, 데이터베이스, 네트워크 시스템에서 발생하는데, 이러한 시스템에서 교착 상태를 피하기 위해 복잡한 알고리즘이 사용된다.
경쟁 상태와 교착 상태의 차이
경쟁 상태와 교착 상태의 차이는 다음과 같다.
경쟁 상태(Race Condition) | 교착 상태(Dead Lock) | |
발생 시점 | 여러 스레드 또는 프로세스가 공유 자원에 접근 및 수정하려고 할 때 발생 | 두 개 이상의 프로세스 또는 스레드가 서로가 가진 자원을 얻기 위해 무한히 대기 |
원인 | 여러 스레드 또는 프로세스가 공유 자원을 동시에 접근하고 수정하려고 할 때 발생하며, 일반적으로 동기화 문제와 관련 | 프로세스나 스레드가 자원을 점유하고 다른 자원을 대기하면서 발생하며, 자원 할당과 관련된 문제로 인해 발생 |
영향도 | 데이터에 일시적으로 잘못된 결과를 가져오거나, 데이터 일관성 문제를 발생시키나 시스템을 완전히 정지시키지는 않음 | 시스템을 완전히 멈추게 만들어 작업 진행이 더이상 어려움 |