여러 개의 프로세스나 스레드가 서로 상대방의 자원을 점유하고 있어 더 이상 진행할 수 없는 상태입니다. 말 그대로 교착상태!
일어나지 않을 사건을 기다리며 무한정 진행이 멈춰 버리는 상황은 왜 발생하는 걸까요?
원형 식탁에서 식사를 하려는 철학자 5명이 있습니다.
이때, 아래의 순서에 따라 음식을 먹게됩니다.
한 두명만 하고 있을땐, 별 문제 없이 진행되겠지만, 5명이 모두 다같이 진행할 경우... 2번에서 막히게 됩니다.
5명이 모두 왼손에 포크를 집고 존재하지 않는 오른쪽 포크를 무한정 기다리는 겁니다.
이때 이러한 무한 대기가 발생하는 원인으로 여러가지를 생각 해볼 수 있습니다.
상호 배제: 포크를 한번에 한 명의 철학자만 이용하게 함
-> 두명의 철학자가 같이 포크를 이용한다면 해결 될 수 있습니다.
점유 및 대기: 왼쪽 포크를 들고 오른쪽 포크를 기다림
-> 왼쪽 포크 이용 완료 후 제자리에 돌려놓고 오른쪽 포크를 이용하게 된다면 해결 될 수 있습니다.
비선점: 한 철학자가 가진 포크를 뺏지 못합니다.
-> 예의 없는 철학자가 다른 철학자의 포크를 뺏어 음식을 먹으면 해결 할 수 있습니다.
원형 대기: 포크를 점유하고 다른 포크를 기다리는 형태가 원의 형태로 그려집니다.
-> 모든 포크에 1번 부터 5번까지 번호를 붙이고, 철학자들이 번호가 낮은 포크에서 높은 포크 순으로 집어들게 한다면 원형 대기는 발생하지 않습니다.
철학자 문제를 코드로 직접 작성하여 해결해보겠습니다.
먼저 철학자가 테이블에 둘러 앉아있는 경우입니다.
다음과 같이 구현을 진행합니다.
import threading
import time
class Philosopher(threading.Thread):
def __init__(
self,
index: int,
left_fork: threading.Semaphore,
right_fork: threading.Semaphore,
):
threading.Thread.__init__(self)
self.index = index
self.left_fork = left_fork
self.right_fork = right_fork
def run(
self,
) -> None: # Thread를 실행할때 start() 함수를 호출하면, Thread 클래스 내부의 run() 함수가 호출됩니다.
print(f"철학자 {self.index}: 배가 고프다")
self.dine()
def dine(self) -> None:
# 왼쪽 포크를 집는다
print(f"철학자 {self.index}: 왼쪽 포크를 집으려고 대기중")
self.left_fork.acquire()
print(f"철학자 {self.index}: 왼쪽 포크를 집음")
time.sleep(1)
# 오른쪽 포크를 집고
print(f"철학자 {self.index}: 오른쪽 포크를 집으려고 대기중")
self.right_fork.acquire()
print(f"철학자 {self.index}: 오른쪽 포크를 집음")
time.sleep(1)
# 식사
print(f"철학자 {self.index}: 식사중")
time.sleep(2)
# 오른쪽 포크를 놓고
self.right_fork.release()
print(f"철학자 {self.index}: 오른쪽 포크를 놓음")
time.sleep(1)
# 왼쪽 포크를 놓음
self.left_fork.release()
print(f"철학자 {self.index}: 왼쪽 포크를 놓음")
time.sleep(1)
print(f"철학자 {self.index}: 식사 완료! 배부르다")
if __name__ == "__main__":
number_of_philosophers = 5
forks = [threading.Semaphore() for n in range(number_of_philosophers)]
philosophers = [
Philosopher(
i,
forks[i % number_of_philosophers],
forks[(i + 1) % number_of_philosophers],
)
for i in range(number_of_philosophers)
]
for philosopher in philosophers:
philosopher.start()
for philosopher in philosophers:
philosopher.join()
해결 방법
if self.index == 0:
# 오른쪽 포크를 집고
print(f"철학자 {self.index}: 오른쪽 포크를 집으려고 대기중")
self.right_fork.acquire()
print(f"철학자 {self.index}: 오른쪽 포크를 집음")
time.sleep(1)
# 왼쪽 포크를 집는다
print(f"철학자 {self.index}: 왼쪽 포크를 집으려고 대기중")
self.left_fork.acquire()
print(f"철학자 {self.index}: 왼쪽 포크를 집음")
time.sleep(1)
else:
# 왼쪽 포크를 집는다
print(f"철학자 {self.index}: 왼쪽 포크를 집으려고 대기중")
self.left_fork.acquire()
print(f"철학자 {self.index}: 왼쪽 포크를 집음")
time.sleep(1)
# 오른쪽 포크를 집고
print(f"철학자 {self.index}: 오른쪽 포크를 집으려고 대기중")
self.right_fork.acquire()
print(f"철학자 {self.index}: 오른쪽 포크를 집음")
time.sleep(1)
def dine(self) -> None:
# 왼쪽 포크를 집는다
print(f"철학자 {self.index}: 왼쪽 포크를 집으려고 대기중")
self.left_fork.acquire()
print(f"철학자 {self.index}: 왼쪽 포크를 집음")
time.sleep(1)
# 오른쪽 포크를 집고
print(f"철학자 {self.index}: 오른쪽 포크를 집으려고 대기중")
if not self.right_fork.acquire(timeout=1):
self.left_fork.release()
print(f"철학자 {self.index}: 왼쪽 포크를 놓음")
time.sleep(1)
self.left_fork.acquire()
print(f"철학자 {self.index}: 왼쪽 포크를 집음")
self.right_fork.acquire()
print(f"철학자 {self.index}: 오른쪽 포크를 집음")
그러면 또 다른 해결방법에는 어떤 것이 있을까요?
안전 상태: 모든 프로세스가 정상적을 자원을 할당 받고 종료 될 수 있는 상태
불안전 상태: 교착 상태가 발생 가능한 상황
안전 순서열: 교착 없이 안전하게 프로세스들에 자원을 할당 할 수 있는 순서
위와 같은 상황에서 안전 상태로 할당을 위해서는 P2->P1->P3순서로 진행이 필요합니다.
먼저 P2에 남은 자원을 모두 할당하고, P2에 할당했던 자원들을 모두 회수 후 P1에 할당하고, 마지막으로 P3에 할당함으로써 교착 상태를 회피하며 모든 프로세스를 실행합니다.