Deadlock 관련 궁금한점이 있습니다.

georgek의 이미지


안녕하세요. 요즘 OS를 공부하다가 궁금한점이 생겨서 여기에 질문을 올려봅니다.

이와같은 함수가 있다고 하는데요. 여기서 두개의 process가 아래 함수를 동시에 호출할때(transaction(saving,checking,amount1) 과 transaction(checking,saving,amount2) ), 잠재적인 deadlock이 발생할 수 있다고 하는데, 어떻게 왜,,, 발생하는 거죠?
===========================================================================
void transaction(Account from, Account to, double amount) {
Semaphore lock1, lock2;
lock1 = getLock(from);
lock2 = getLock(to);

wait(lock1);
wait(lock2);

withdraw(from, amount);
deposit(to, amount);

signal(lock2);
signal(lock1);

}
===========================================================================

저의 생각은 일단 아래와 같습니다.
p1 / p2
lock1->saving/ lock1->checking (이부분이 동시에 먼저 wait부분에서 걸림)
lock2->checking / lock2->saving (이부분에서 동시에 wait부분에서 걸림)
=> saving,checking이 block()됨. 빠져나오질 못함. deadlock...
저도 이렇게 말은 하는데 느낌으론 알겠는데 어떻게 말을 해야할지 모르겠네요 ㅜㅜ

이게 왜 deadlock이 걸리고, 이걸 해결을 어떻게 해야하는지 알려주실수 있을까요? (조언이라도 감사하겠습니다.)

감사합니다!!

chanik의 이미지

http://stackoverflow.com/questions/17415274/c-how-can-this-simple-transaction-function-be-completely-free-of-deadlocks

질문하신 샘플과 유사한 코드에 대해 같은 질문을 하고 있고 해결책도 나오는군요.

요점은 두 개의 락이 결부되어 있는 상황에서 데드락에 빠지지 않으려면
경쟁하는 모든 프로세스/스레드들이 두 개의 락을 같은 순서로 걸어야 한다는 것입니다.

계좌A와 계좌B가 있는 상황에서
프로세스1은 계좌A로부터 계좌B로 자금을 옮기려고 하고,
프로세스2는 계좌B로부터 계좌A로 자금을 옮기려고 한다면,
각 프로세스는 계좌A의 락A와 계좌B의 락B 모두를 필요로 합니다.

그런데, 예로 드신 transaction() 함수와 같이 구현하게 되면
프로세스A는 락A를 먼저 걸고 락B를 나중에 걸려고 하는 반면,
프로세스B는 락B를 먼저 걸고 락A를 나중에 걸려고 할 것입니다.
동시에 진행되던 두 프로세스가 어쩌다 각자 락 하나씩을 거는데 성공하고 나면
이젠 둘다 서로 상대방이 쥐고있는 락을 기다려야 하므로 데드락에 빠지는 것이죠.

이 상황을 피라려면 두 프로세스가 모두 같은 순서로 락을 걸어야 합니다.

위 링크의 자료에서는 계좌번호를 비교해서 계좌번호가 작은 계좌의 락을 먼저 거는 식으로
순서를 정해서 문제를 해결하고 있습니다.

링크만 걸었다가, 간략히 적으려고 말을 보탠건데 막상 적고보니 장황하네요..

georgek의 이미지

프로세스A는 락A를 먼저 걸고 락B를 나중에 걸려고 하는 반면,프로세스B는 락B를 먼저 걸고 락A를 나중에 걸려고 할 것입니다.
동시에 진행되던 두 프로세스가 어쩌다 각자 락 하나씩을 거는데 성공하고 나면 이젠 둘다 서로 상대방이 쥐고있는 락을 기다려야 하므로 데드락에 빠지는 것이죠.
=> 여기서 각각 두번째에 걸리는 락에서 block이 되어버려서 데드락에 걸려버리는 것인가요?
두 프로세스가 동시에 함수를 호출한다고 하면, 각각의 함수에서 wait(lock1), wait(lock2)도 각각 순서대로 동시에 호출을 하는건가요?
P_A / P_B
1)lock_a / lock_b
2)lock_b / lock_a
이런식으로 엇갈리게 wait을 걸게되면서,, checking, saving 리소스가 결국 락에 걸려버린다고 말하면 맞는말인가요?

어떻게 락에 걸리는 상황이 걸리게되는지 말하는게 어렵네요 ㅜㅜ

mirheekl의 이미지

함수 구현상 lock1이랑 lock2는 그냥 이름일 뿐, 실질적인 순서가 보장이 되어 있지 않습니다. 즉 계좌1, 계좌2 순으로 넘길 수도 있고, 말씀하신 대로 계좌2, 계좌1 순으로 파라미터를 넘길수도 있는 것이죠.

이때

프로세스 1              프로세스 2 

claim(계좌1); claim(계좌2); -> 여기까지 수행하고
claim(계좌2); claim(계좌1); -> 여기서 데드락이 걸리는 겁니다. 프로세스1은 계좌2를 기다리고 있고 프로세스2는 계좌1을 기다리고 있으니까요.
....... .........
release(계좌2); release(계좌1);
release(계좌1); release(계좌2);

해결방법은 위에 stackoverflow 링크로 보여주셨듯이 락 오브젝트의 순서를 고정해주시면 됩니다. 아니면 아예 두 개의 claim을 동기화하든지 해서 한개만 얻고 딴쪽으로 스위칭하는 상황을 없애도 되겠지만 효율 문제가 있을 듯.

또는, 조금 허황된 방법이긴 하지만 계좌 오브젝트에 락을 거는 대신 돈에다 락을 거는 방법도 있습니다. 돈 오브젝트라면 한번에 두 곳 이상에서 터치할 일이 전혀 없으니 안전하게 락을 걸 수 있습니다. 가령 만원짜리 오브젝트가 있다면 그놈에 락을 걸고 계좌 1에서 계좌 2로 소속을 바꾸는 식이죠. (너무 진지하게 받아들이진 마세요... 하지만 가끔 이런 잔머리가 유용할때도 있습니다.)

--

chanik의 이미지

Quote:
여기서 각각 두번째에 걸리는 락에서 block이 되어버려서 데드락에 걸려버리는 것인가요?
두 프로세스가 동시에 함수를 호출한다고 하면, 각각의 함수에서 wait(lock1), wait(lock2)도 각각 순서대로 동시에 호출을 하는건가요?
P_A / P_B
1)lock_a / lock_b
2)lock_b / lock_a
이런식으로 엇갈리게 wait을 걸게되면서,, checking, saving 리소스가 결국 락에 걸려버린다고 말하면 맞는말인가요?

생각하신 바가 맞습니다.

P_A는 wait(lock_a)를 성공시키고 나면 바로 wait(lock_b)에 돌입하게 되고
P_B는 wait(lock_b)를 성공시키고 나면 바로 wait(lock_a)에 돌입하게 되죠.

만약 우연히 두 프로세스가 동시에 진행되다가 각각 첫번째 wait()에 성공하게 되면
P_A는 lock_a를 절대로 놓지 않으면서 wait(lock_b)를 실행합니다.
P_B는 lock_b를 절대로 놓지 않으면서 wait(lock_a)를 실행하죠.

결국 서로에게 필요한 락을 하나씩 붙들고 절대로 놓지 않으면서도
상대방이 붙들고 있는 락마저 얻으려고 무한정 wait()하는 상황에 빠지게 되죠.
이런 상황이 데드락이고요.

데드락 위치는 경쟁중인 두 프로세스의 두 번째 wait() 지점이고,
그 때문에 생기는 결과는, 계좌A와 계좌B가 관련된 이후의 모든 프로세스들도
덩달아 무한정 wait()에 빠지게 되므로 두 계좌가 결부된 업무가 몽땅 마비된다는 것입니다.
계좌X <-> 계좌A간의 이체, 계좌B <-> 계좌Y 사이의 이체 등...

--------

해결책에 대해 다시 말씀드리자면,
지금의 상황은 기계적으로 출금계좌의 락을 먼저 걸고 입금계좌의 락을 나중에 거는 방식때문에 생긴 문제입니다.
입/출금 순서가 아닌 계좌번호 순서대로 락을 걸게 되면
두 프로세스는 우선 lock_a(A 계좌의 번호가 더 낮은 숫자라고 가정)에서 1차 경쟁을 하고
거기서 순서가 밀리게 되면 승자가 나머지 lock_b를 얻어 일처리를 끝낼때까지 얌전히 기다리게 되므로
서로 락이 꼬여 데드락에 빠지는 상황을 피하게 되는 것입니다.

다른 분의 댓글도 있고 해서 간단히 적으려고 시작한 건데, 적고보니 또 길어졌네요..

georgek의 이미지

이제 정확히 이해를 했네요 정말 감사합니다!!