이전 포스트에서 한정된 공유 자원에 대한 접근을 관리하는 Lock에 대해서 공부했다. Lock을 사용할 때에는 Dead Lock이 발생하지 않도록 주의해야 한다.
교착 상태 (Dead Lock)이란, 두 개 이상의 작업이 서로 상대방의 작업이 끝나기 만을 기다리고 있기 때문에 결과적으로 아무것도 완료되지 못하는 상태를 가리킨다.
데드 락을 발생시키기 위해서는 네 가지 조건을 만족해야 한다.
어떤 한 대륙이 있다.
편의를 위해 해당 대륙은 가로로 길고 총 100 단위의 땅을 가지고 있으며, 그 중 1부터 50까지의 땅은 A 국가에, 51부터 100까지의 땅은 B 국가에 속해있다고 가정한다.
a_land = list(range(1,51))
b_land = list(range(51,101))
각 국가는 대륙을 정찰하기 위해 정찰병을 파견하였다.
A 국가의 정찰병은 1부터 순서대로 100까지 올라가고, B 국가의 정찰병은 100부터 순서대로 1까지 내려간다.
a_scout = 0
b_scout = 99
각 국가는 서로 정찰병을 파견했다는 것을 알고 있지만, 정찰병의 정찰 속도를 알지 못한다. 따라서 본인의 국가를 정찰하는 것에 대해 배타적인 권리를 행사하도록 한다.
a_lock = threading.Lock()
b_lock = threading.Lock()
즉, 본인 국가에 대한 정찰이 완료된 이후에야 타 국가에게 정찰 권리를 넘긴다는 의미이다.
각 국가의 정찰병은 다음과 같은 방법으로 정찰을 수행한다.
class A_Scouting(threading.Thread):
def __init__(self):
super().__init__()
def run(self):
global a_scout
a_lock.acquire()
for i in range(50):
a_land[a_scout-50] = "A"
a_scout += 1
time.sleep(0.1)
b_lock.acquire()
a_lock.release()
for i in range(50):
b_land[a_scout] = "A"
a_scout += 1
time.sleep(0.1)
b_lock.release()
우선 정찰병은 A 국가의 정찰에 대해 배타적 권리를 행사한다. (a_lock.acquire)
A 국가의 정찰병은 0부터 차례대로 정찰하면서 정찰이 완료된 토지에 대해서는 "A"
라는 표식을 남긴다. 그리고 휴식을 취한다.
그러나 B 국가에게 A국에게 정찰 권한을 넘겼음에도 불구하고 B 국가가 B국의 정찰 권한을 넘기지 않을 수도 있기 때문에, B국에 대한 정찰 권한을 먼저 받고 (b_lock.acquire
) 나서야 A국의 정찰 권한을 넘기고자한다.(a_lock.release
)
물론 이는 B 국가도 똑같이 생각하고 있다.
자 그러면, 각 국가가 각 국가의 토지에 대한 정찰을 마치고 난 이후에는 어떤 일이 발생할까?
A 국가는 A 국가에 대한 정찰을 모두 완료했으니 B 국가에 대해 정찰 권리를 요구한다.
B 국가는 B 국가에 대한 정찰을 모두 완료했으니 A 국가에 대해 정찰 권리를 요구한다.
그러나 A 국가는 B 국가에게 정찰 권리를 넘기기 이전에 B 국가의 정찰권리를 먼저 받고 싶어하고, B 국가 역시 A 국가에게 정찰 권리를 넘기기 이전에 A 국가의 정찰 권리를 먼저 받고 싶어 한다.
서로가 서로의 정찰 권리를 요구하고 있으므로 협상은 진전되지 않고 교착 상태에 빠지게 된다.
앞서 언급한 교착 상태의 네 가지 필요조건에 대입하여 생각해보자.
a_land
)의 정찰권은 A가 가지고 있으며, B 국가의 토지(b_land
)의 정찰권은 B가 가지고 있다. 그리고 각 정찰권리는 각 국가가 배타적으로 행사한다. 앞선 네 가지 조건 중에서 한 가지 조건이라도 만족하지 못한다면 교착 상태는 해제된다.
가장 간단한 방법은 두 국가 모두 정찰 권리를 포기하는 것이다. 즉, 락을 걸지 않는 것이다. 그럴 경우 정찰에 필요한 자원(정찰권)이 없어지기 때문에, 네 가지 조건이 동시에 해제된다.
그러나 정찰권이 있는 상태에서 협상하고자 한다면 두 번째 조건 (점유 대기)를 해제 하면 된다.
즉, 본 국가에 대한 정찰권을 가지고 있는 상태에서 타 국의 정찰권을 요구하는 것 보다는, 본 국에 대한 정찰권을 양도하고 타 국의 정찰권을 받아오면 된다.
이를 코드로 구현하면 다음과 같이 수정할 수 있다.
class A_Scouting(threading.Thread):
def __init__(self):
super().__init__()
def run(self):
global a_scout
a_lock.acquire()
for i in range(50):
a_land[a_scout] = "A"
a_scout += 1
time.sleep(0.1)
a_lock.release() # 먼저 정찰권을 넘겨주고
b_lock.acquire() # 타 국의 정찰권을 받아온다.
for i in range(50):
b_land[a_scout-50] = "A"
a_scout += 1
time.sleep(0.1)
b_lock.release()
그렇다면 두 국가 모두 두 국가가 속한 대륙에 대한 정찰을 완료할 수 있다.