Backend study/Backend theory

경쟁 조건(Race Condition)과 교착 상태(Deadlock)

adulty22 2024. 9. 17. 06:28

경쟁 조건(Race Condition)교착 상태(Deadlock)는 멀티스레드 또는 멀티프로세스 환경에서 자주 발생하는 동시성 문제이다. 이 두 개념은 동시성 프로그래밍에서 데이터의 무결성과 시스템의 안정성을 위협할 수 있는 중요한 문제이다.

 

1. 경쟁 조건 (Race Condition)

경쟁 조건이란?

경쟁 조건은 두 개 이상의 스레드 또는 프로세스가 동시에 공유 자원에 접근하여, 그 실행 순서에 따라 프로그램의 결과가 달라질 수 있는 상황을 말한다. 즉, 여러 스레드나 프로세스가 동일한 자원을 동시에 접근하고 수정하는 경우, 그 결과는 실행 순서에 의해 비정상적인 상태로 변할 수 있다.

경쟁 조건의 발생 원인

경쟁 조건은 일반적으로 공유 자원에 대한 비동기적 접근으로 인해 발생한다. 여러 스레드가 동시에 하나의 자원을 읽고 쓰려고 할 때, 그 시점에 따라 잘못된 값을 읽거나 저장할 수 있다. 이런 상황에서는 한 스레드가 자원을 사용하기 전에 다른 스레드가 그 자원을 수정할 수 있어 의도하지 않은 결과가 발생한다.

경쟁 조건의 예시

다음은 경쟁 조건이 발생할 수 있는 코드 예시이다. 두 개의 스레드가 하나의 공유 변수 counter를 동시에 증가시키려고 한다.

counter = 0

def increment():
    global counter
    for i in range(1000000):
        counter += 1

# 두 스레드가 동시에 실행됨
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)

thread1.start()
thread2.start()

thread1.join()
thread2.join()

print(counter)

여기서 두 개의 스레드가 counter 변수를 동시에 증가시키려고 할 때, 각 스레드가 counter의 값을 읽고 증가한 다음, 다시 값을 쓰는 과정에서 경쟁 조건이 발생할 수 있다. 이로 인해 결과가 예상과 달리 2,000,000이 아닌 더 작은 값이 나올 수 있다.

경쟁 조건 해결 방법

경쟁 조건을 해결하려면 동기화 기법을 사용하여 여러 스레드가 공유 자원에 동시에 접근하지 못하도록 해야 한다. 다음은 주요 동기화 방법들이다:

  • 뮤텍스(Mutex): 한 번에 하나의 스레드만 자원에 접근할 수 있도록 잠금(Lock)을 사용한다. 다른 스레드가 자원에 접근하려면 잠금이 해제될 때까지 기다린다.
lock = threading.Lock()

def increment():
    global counter
    for i in range(1000000):
        with lock:
            counter += 1
  • 세마포어(Semaphore): 동시에 일정 개수의 스레드가 자원에 접근할 수 있도록 제한한다. Mutex가 1개의 스레드만 허용한다면, 세마포어는 N개의 스레드를 허용할 수 있다.
  • 모니터(Monitor): 객체 자체에 동기화 메커니즘을 포함시켜, 내부적으로 자원의 동기화를 관리한다.

 

2. 교착 상태 (Deadlock)

교착 상태란?

교착 상태는 두 개 이상의 프로세스 또는 스레드가 서로 자원을 점유한 상태에서, 상대방이 점유한 자원을 필요로 하여 무한정 기다리게 되는 상황을 의미한다. 이로 인해 시스템이 아무런 진행도 없이 멈추는 상태가 된다.

교착 상태의 발생 조건

교착 상태는 4가지 조건이 동시에 만족될 때 발생한다. 이를 교착 상태의 필요 조건이라고 한다:

  1. 상호 배제 (Mutual Exclusion): 자원은 한 번에 하나의 프로세스만 사용할 수 있다.
  2. 점유와 대기 (Hold and Wait): 자원을 점유한 프로세스가 다른 자원의 할당을 기다리고 있다.
  3. 비선점 (No Preemption): 다른 프로세스가 점유한 자원을 강제로 빼앗을 수 없다. 자원은 사용한 프로세스가 자발적으로 해제해야 한다.
  4. 순환 대기 (Circular Wait): 두 개 이상의 프로세스가 원형으로 자원을 대기하고 있다. 즉, 프로세스 A는 프로세스 B가 점유한 자원을 기다리고, 프로세스 B는 다시 프로세스 A가 점유한 자원을 기다리는 상황이다.

교착 상태의 예시

교착 상태는 두 개의 스레드가 각각 자원 A와 자원 B를 점유하고, 서로 상대방의 자원을 요구할 때 발생할 수 있다.

import threading

lockA = threading.Lock()
lockB = threading.Lock()

def thread1():
    with lockA:  # 자원 A를 점유
        print("Thread 1: acquired lockA")
        with lockB:  # 자원 B를 기다림
            print("Thread 1: acquired lockB")

def thread2():
    with lockB:  # 자원 B를 점유
        print("Thread 2: acquired lockB")
        with lockA:  # 자원 A를 기다림
            print("Thread 2: acquired lockA")

t1 = threading.Thread(target=thread1)
t2 = threading.Thread(target=thread2)

t1.start()
t2.start()

t1.join()
t2.join()

여기서 thread1은 lockA를 점유하고 lockB를 기다리며, thread2는 lockB를 점유하고 lockA를 기다린다. 이 상황에서 두 스레드는 서로 상대방이 해제할 자원을 기다리며 교착 상태에 빠진다.

교착 상태 해결 방법

  1. 교착 상태 예방 (Prevention)
    • 상호 배제 부정: 가능한 한 자원을 공유하여 상호 배제 조건을 피한다. 예를 들어, 읽기 전용 자원은 동시에 여러 스레드가 접근할 수 있게 한다.
    • 점유와 대기 방지: 자원을 점유할 때 추가 자원을 요구하지 못하게 하거나, 필요한 모든 자원을 한꺼번에 할당한다.
    • 비선점 조건 제거: 자원을 점유한 프로세스가 다른 자원을 요구하면, 이미 점유한 자원을 해제하도록 한다.
    • 순환 대기 방지: 자원에 고유한 순서를 부여하고, 프로세스가 자원을 할당받는 순서를 준수하도록 한다.
  2. 교착 상태 회피 (Avoidance)
    • 은행가 알고리즘(Banker's Algorithm): 프로세스가 자원을 요청할 때, 시스템이 자원이 부족하여 교착 상태에 빠질 위험이 있는지 미리 계산하여 자원의 할당을 결정하는 방식이다.
    • 자원의 요청을 수락하기 전에, 시스템이 교착 상태에 빠질 가능성을 피할 수 있는지 평가한다.
  3. 교착 상태 탐지 (Detection) 및 회복 (Recovery)
    • 탐지: 교착 상태를 탐지하기 위해 주기적으로 자원 할당 그래프를 분석한다. 순환 대기 상태가 존재하는지 확인한다.
    • 회복: 교착 상태를 해결하기 위해, 하나 이상의 프로세스를 중단하거나 자원을 강제로 선점하여 문제를 해결한다. 주로 교착 상태에 있는 프로세스 중 하나를 강제로 종료하는 방식으로 회복을 시도한다.

 

교착 상태와 경쟁 조건의 차이

 

특징  경쟁 조건 (Race Condition)  교착 상태 (Deadlock)
발생 조건 두 스레드가 공유 자원에 동시에 접근할 때 발생 두 개 이상의 스레드가 서로 자원을 기다리며 무한정 대기할 때 발생
결과 실행 순서에 따라 결과가 달라짐 (데이터 불일치 발생) 프로세스나 스레드가 멈추고 더 이상 진행되지 않음
해결 방법 동기화 메커니즘 사용 (뮤텍스, 세마포어 등) 예방, 회피, 탐지 및 회복 방법 사용
주요 특징 실행 순서에 의해 의도하지 않은 결과가 발생 자원 대기 때문에 프로그램이 정지 상태에 빠짐

 

  • 경쟁 조건(Race Condition)은 여러 스레드 또는 프로세스가 동시에 자원에 접근할 때 발생하는 문제로, 실행 순서에 따라 잘못된 결과가 나올 수 있다. 이를 해결하기 위해서는 동기화 메커니즘(뮤텍스, 세마포어 등)을 사용하여 공유 자원에 대한 접근을 제어해야 한다.
  • 교착 상태(Deadlock)는 여러 프로세스 또는 스레드가 서로의 자원을 기다리며 무한정 대기하는 상태로, 시스템이 더 이상 진행되지 않는다. 교착 상태는 예방, 회피, 탐지 및 회복 기법을 통해 해결할 수 있다.

이 두 문제는 동시성 프로그래밍에서 매우 중요한 이슈이며, 시스템의 안정성과 데이터의 무결성을 보장하기 위해 반드시 적절한 대처가 필요하다.

728x90