미리 밝혀두지만 rein이 정말 본격적으로 Windows 시스템 프로그래밍을 한 것은 작년 부터다. 그런 의미에서 겪었던 삽질 하나를 밝혀둔다. ((덤으로 금요일 저녁에 Rica한테 잘못된 사실을 하나 말했던 것을 정정하려한다))
Linux system에서 널리 사용되는 posix thread (이하 pthread) 라이브러리의 가장 기본적인 동기화 메커니즘은 pthread_mutex_t 라는 타입으로 불리는 일종의 mutex다. ((물론 spin–lock도 pthread_spinlock_t로 제공된다)) 이걸 쓰던 사람이 Windows의 CRITICAL_SECTION 이나 Win32 mutex를 사용할 때 가장 실수하기 쉬운 것.
Win32의 CRITICAL_SECTION이나 mutex는 recursive하게 ((재귀적으로. 즉 thread A가 어떤 mutex나 CS의 락을 얻은 상태에서 또 해당 락을 얻으려고 시도하면 잡을 수 있다)) lock을 잡는 것을 허용한다 ((내가 Rica한테 잘못 설명한 부분이 여기. 난 Mutex는 재귀적으로 못잡는다고 생각했다. 근데 되더라고…))
거꾸로 Win32 프로그래머가 linux pthread를 쓴다면 주의할 것,
pthread의 mutex는 기본적으로 recursive–locking을 허용하지 않는다
즉, 다음과 같은 코드는 영원히 정지한다[…]
pthread_mutex_lock( &lock );
pthread_mutex_lock( &lock ); // 이 줄을 벗어날 수 없다pthread_mutex_unlock( &lock );
pthread_mutex_unlock( &lock );
Win32에 익숙한 프로그래머라면 내가 주석처리한 저 코드를 보고 갸웃할지도 모른다. 그렇지만 pthread_mutex의 기본 동작은 recursive–locking에 대한 거부다. 그렇지만 다음과 같은 방식으로 mutex를 생성하면 recursive–locking을 허용하게 만들 수 있다.
pthread_mutexattr_t attr;
pthread_mutexattr_init( &attr );
int val = PTHREAD_MUTEX_RECURSIVE_NP;
pthread_mutexattr_settype( &attr, val);
pthread_mutex_init( &lock, &attr );
이런 조금 귀찮은 과정을 거치면 recursive–locking을 지원하는 — win32 프로그래머가 좀 더 익숙해 할, 혹은 그런 종류의 lock이 필요한 상황에서 써야하는 — pthread용 mutex가 생성된다.
근본(…)이 *nix 세계의 사람인 rein은 ((시스템 프로그래밍을 Solaris 2.x에서 시작해서 Redhat과 Fedora를 거쳐 지금에 이르렀다)) pthread의 기본 동작을 조금 더 좋아한다. 이유는 예전에 설명했던 Dead-lock;데드락을 막는 locking protocol ((Lock들에 번호를 부여하고, 특정 순서로만 lock잡는걸 허용하는 것))때문이다. Win32의 기본적인 recursive–locking에서는 다음 시나리오에서 필패한다(잠재적인 dead–lock을 허용한다)
- lock L2
- … some code block …
- lock L1
- lock L2
- lock L3
- …
이 경우에 pthread_mutex라면 4에서 이미 프로그램이 멈추기 때문에(기본적인 pthread_mutex 동작에서), 상대적으로 빨리 눈치채고 대응할 수 있다. (아마도 프로그램이 릴리즈 되기 전에). 반면에 Win32환경에서는 운만 좋으면 ((3이 실행되기전에 L1을 잡는 스레드가 없기만 하면된다)) — 운이 나쁘다고 봐도 된다 — 릴리즈되기 전에 데드락이 숨어있다는 것을 알 수 없게 된다.
물론 win32도 좀 쓰기 시작한지라 lock 전체의 잡는 순서를 로깅하는 간단한 per-thread 객체를 하나 써서 lock순서를 추적하기 때문에, 이런 일은 바로 눈치채고 있긴하지만, 가끔은 pthread_mutex가 그리워지기도 해서 이런 글을 끄적대 본다.
ps. 덤으로 intel TBB의 hash_table의 숨겨져있는 lock들은 pthread의 convention을 따른다. 즉 두 번 잡으려고하면 해당 스레드는 거의 영원히 멈추게 된다. 그런 의미에서 주의해야.
아…. 윈도우에서는 recursive locking이 되는군요. 몰랐네요.
저같은 경우에는 반대로 pthread가 기본적으로는 recursive–locking가 안 된다는 사실을 알게 되었네요. 군주님 짱 ;ㅁ;
피앙 / 모르고 지뢰 밟는거다. ㄱㄱ
Azyu / 후후후(…)
아실 것 같은데… Win32용 pthread 포팅이 있습니다. 거기보면 거의 동일한 semantic으로 구현이 되어있습니다. Recursive 행동도 당연히 pthread와 같이 하고 있구요 :)
하지만 회사에선 Win32 로 구성된 라이브러리라서 :)
저야 pthread쓰면 편하지만 그렇게하긴 힘들죠 흑흑
win32 프로그래밍만 거의 했는데, recursive 로 되는 줄 몰랐네요 감사합니다…… 지뢰라는 표현 너무 재밌어요 :)
신경 안쓰다가 가끔 데드 락을 만드는 경우가 생기더군요(…) 흑흑.
그런 의미에서 지뢰는 지뢰입죠(?)
win32 에서 작업한 코드가 dead 락이 되는 것을 보고 의아해 했는데 좋은 코드 조각 포스팅과 설명때문에 시간 절약했습니다~ 감사 __*
좋은 글 감사합니다.