라이브락 (Livelock)

멀티스레딩 환경에서 발생할 수 있는 문제 상황으로, 프로세스나 스레드가 계속 실행 중이지만 실제로는 유용한 작업을 수행하지 못하는 상태

라이브락의 특징:

  1. 진행 중 상태: 프로세스나 스레드가 ‘실행 중’ 상태를 유지한다.
  2. 무의미한 작업: 실제로는 어떠한 유용한 작업도 수행하지 못한다.
  3. 반복적 상태 변경: 특정 조건을 만족시키기 위해 상태를 계속 변경하지만 원하는 결과를 달성하지 못한다.

라이브락과 데드락의 차이:

  • 데드락: 프로세스들이 서로의 자원을 기다리며 완전히 멈춘 상태
  • 라이브락: 프로세스들이 계속 실행되지만 실제로는 진전이 없는 상태

라이브락의 예시:

  1. 복도에서 마주친 두 사람: 서로 지나가려고 같은 방향으로 계속 이동하지만 결국 지나가지 못하는 상황
  2. 프로세스 간 자원 경쟁:
    • 프로세스 A가 자원 Y를 보유하고 X를 필요로 함
    • 프로세스 B가 자원 X를 보유하고 Y를 필요로 함
    • 두 프로세스가 서로의 자원을 기다리며 계속 상태를 변경하지만 진전이 없음
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import threading
import time

class Philosopher:
    def __init__(self, name, left_fork, right_fork):
        self.name = name
        self.left_fork = left_fork
        self.right_fork = right_fork
        
    def try_eat(self):
        while True:  # 계속해서 시도
            if self.left_fork.acquire(timeout=1):  # 왼쪽 포크 잡기 시도
                print(f"{self.name}이(가) 왼쪽 포크를 집었습니다")
                
                if self.right_fork.acquire(timeout=1):  # 오른쪽 포크 잡기 시도
                    print(f"{self.name}이(가) 식사를 시작합니다")
                    time.sleep(1)  # 식사하는 시간
                    self.right_fork.release()
                
                self.left_fork.release()
                print(f"{self.name}이(가) 포크를 내려놓고 다시 시도합니다")
                time.sleep(0.1)  # 다른 철학자에게 기회를 주기 위한 대기
            else:
                print(f"{self.name}이(가) 포크를 얻지 못해 다시 시도합니다")
                time.sleep(0.1)  # 재시도 전 대기

# 테스트 코드
fork1 = threading.Lock()
fork2 = threading.Lock()

philosopher1 = Philosopher("철학자1", fork1, fork2)
philosopher2 = Philosopher("철학자2", fork2, fork1)

# 두 철학자가 동시에 식사하려 시도
t1 = threading.Thread(target=philosopher1.try_eat)
t2 = threading.Thread(target=philosopher2.try_eat)

t1.start()
t2.start()

이 코드에서 두 철학자는 모두 활발히 행동하고 있지만(포크를 집었다 놨다 하면서), 실제로 식사는 하지 못하는 라이브락 상황이 발생할 수 있다.

라이브락 해결 방안:

  1. 타임아웃 도입: 일정 시간 후 재시도하도록 설정

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    class TimeoutBasedResource:
        def __init__(self, timeout=5):
            self.timeout = timeout
            self.lock = threading.Lock()
            self.start_time = None
    
        def acquire(self):
            if self.start_time and time.time() - self.start_time > self.timeout:
                # 시간 초과시 강제 해제
                self.release()
            return self.lock.acquire()
    
  2. 무작위성 도입: 상태 변경에 무작위 지연을 추가하여 동시성 문제 해결

    1
    2
    3
    4
    
    def resource_allocation_with_randomness():
        retry_delay = random.uniform(0, 1)  # 0-1초 사이의 무작위 대기
        time.sleep(retry_delay)
        return try_allocate_resource()
    
  3. 우선순위 조정: 프로세스나 스레드의 우선순위를 동적으로 조정

    1
    2
    3
    4
    5
    6
    7
    8
    
    class PrioritizedProcess:
        def __init__(self, priority):
            self.priority = priority
    
        def attempt_action(self, resource):
            if self.priority > resource.current_user_priority:
                return resource.allocate_to(self)
            return False
    
  4. 의존성 최소화: 프로세스 간 의존성을 줄이는 설계 적용

라이브락 방지를 위한 모범 사례:

  1. 재시도 메커니즘 개선:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    def improved_retry_mechanism(max_attempts=3):
        attempt = 0
        while attempt < max_attempts:
            try:
                if perform_action():
                    return True
            except ResourceBusyException:
                backoff_time = (2 ** attempt) * random.uniform(0, 1)
                time.sleep(backoff_time)
                attempt += 1
        return False
    
  2. 리소스 할당 순서 정규화:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    class ResourceManager:
        def __init__(self):
            self.resources = []
            self.resource_order = {}
    
        def acquire_resources(self, needed_resources):
            # 리소스를 항상 정해진 순서대로 획득
            sorted_resources = sorted(needed_resources, 
                                    key=lambda r: self.resource_order[r])
    
            for resource in sorted_resources:
                if not resource.acquire():
                    # 실패시 이미 획득한 리소스 해제
                    self.release_resources(sorted_resources)
                    return False
            return True
    

라이브락 예방:
3. 명확한 상태 변경 조건 정의
4. 시스템 설계 시 예상치 못한 상호작용 고려
5. 주기적인 시스템 모니터링 및 분석 수행


참고 및 출처