pthread 프로그래머가 Win32에서 밟기 쉬운 지뢰 하나

미리 밝혀두지만 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을 허용한다)

  1. lock L2
  2. … some code block …
  3. lock L1
  4. lock L2
  5. 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을 따른다. 즉 두 번 잡으려고하면 해당 스레드는 거의 영원히 멈추게 된다. 그런 의미에서 주의해야.

Jinuk Kim
Jinuk Kim

SW Engineer / gamer / bookworm / atheist / feminist

Articles: 935

9 Comments

  1. 저같은 경우에는 반대로 pthread가 기본적으로는 recursive–locking가 안 된다는 사실을 알게 되었네요. 군주님 짱 ;ㅁ;

  2. 아실 것 같은데… Win32용 pthread 포팅이 있습니다. 거기보면 거의 동일한 semantic으로 구현이 되어있습니다. Recursive 행동도 당연히 pthread와 같이 하고 있구요 :)

  3. 하지만 회사에선 Win32 로 구성된 라이브러리라서 :)

    저야 pthread쓰면 편하지만 그렇게하긴 힘들죠 흑흑

  4. win32 프로그래밍만 거의 했는데, recursive 로 되는 줄 몰랐네요 감사합니다…… 지뢰라는 표현 너무 재밌어요 :)

    • 신경 안쓰다가 가끔 데드 락을 만드는 경우가 생기더군요(…) 흑흑.

      그런 의미에서 지뢰는 지뢰입죠(?)

  5. win32 에서 작업한 코드가 dead 락이 되는 것을 보고 의아해 했는데 좋은 코드 조각 포스팅과 설명때문에 시간 절약했습니다~ 감사 __*

Leave a Reply