라이브락 (Livelock)#
멀티스레딩 환경에서 발생할 수 있는 문제 상황으로, 프로세스나 스레드가 계속 실행 중이지만 실제로는 유용한 작업을 수행하지 못하는 상태
라이브락의 특징:#
- 진행 중 상태: 프로세스나 스레드가 ‘실행 중’ 상태를 유지한다.
- 무의미한 작업: 실제로는 어떠한 유용한 작업도 수행하지 못한다.
- 반복적 상태 변경: 특정 조건을 만족시키기 위해 상태를 계속 변경하지만 원하는 결과를 달성하지 못한다.
라이브락과 데드락의 차이:#
- 데드락: 프로세스들이 서로의 자원을 기다리며 완전히 멈춘 상태
- 라이브락: 프로세스들이 계속 실행되지만 실제로는 진전이 없는 상태
라이브락의 예시:#
- 복도에서 마주친 두 사람: 서로 지나가려고 같은 방향으로 계속 이동하지만 결국 지나가지 못하는 상황
- 프로세스 간 자원 경쟁:
- 프로세스 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
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()
|
무작위성 도입: 상태 변경에 무작위 지연을 추가하여 동시성 문제 해결
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()
|
우선순위 조정: 프로세스나 스레드의 우선순위를 동적으로 조정
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
|
의존성 최소화: 프로세스 간 의존성을 줄이는 설계 적용
라이브락 방지를 위한 모범 사례:
재시도 메커니즘 개선:
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
|
리소스 할당 순서 정규화:
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. 주기적인 시스템 모니터링 및 분석 수행
참고 및 출처#