멀티스레딩과 STL

C++은 일반화 프로그래밍; generic programming 을 지원하는 언어다. 임의의 타입 T에 대한 클래스, 함수 등을 지정할 수 있고, 이를 컴파일 타임에 코드를 만들어내서1 사용할 수 있게 해준다. 사실 이런 부분이 가장 잘 반영되어 있는 것이 standard template libaray; STL 이고, 얼마 전에 확정된 tr1에 포함된 수많은 boost library 들이다.

이 중에서 STL과 멀티스레딩; multithreading 에 관해 한 가지 얘기를 해보려한다.IRC 채널에 STL의 스레드 안전성에 관한 뻘글, 또 다른 뻘글의 링크가 올라오기도 했었고, 오늘은 GPG 스터디 포럼에 STL 메모리 단편화 문제에 관한 질문이 올라 오면서 멀티스레딩 얘기가 잠깐 나오기도 했다 (중간의 답변글 중 하나가 너무 간단히 스레드 안전하지 않아 라고 말하고 있다).

위에서 링크한 STL 페이지에서도 말하고 있지만, STL에서 보장해주는 스레드 안전성; thread-safety 은 굉장히 단순하다. C++에서 int; integer 타입에서 기대할 수 있는만큼의 스레드 안전성을 보장한다2.

간단히 설명하면 이렇다. 우선 STL 컨테이너 A, B가 있고 스레드 t1, t2, t3가 있다고 하자.

  • t1, t2, t3가 _동시_에 A를 읽는 것은 안전하다. 혹은 t1, t2는 A를 읽고 t3는 B를 읽는경우도 안전하다 – 한 객체를 동시에 읽는 것은 안전
  • t1이 A에 쓰고, t2는 B에 쓰는 것도 안전하다 – 같은 객체에 동시에 쓰는 것이 아니다
  • t1이 A에 쓰고, t2, t3는 B를 읽는 것도 안전하다 – 관계 없는 객체사이에선 무슨 짓을 해도 무방

…이런 식으로 int에 기대할 수 있는 수준으로는 기대해도 좋다. (그러니까 딱 상식적인 수준의 스레드 안전성을 보장한다)

왜 이 이상을 못하는지도 모두 꼭 한번씩 생각해보자. vector<some_type> v 라는 변수가 있다고 해보자. 만약 이런 코드를 실행시키면 어떨까? (미리 2 개 이상의 원소가 들어있는 상태였다고 하자)

Thread 1에서 if( v[0] == v[1] ) 어쩌구; 를 실행

Thread 2에서 if( v[1] == v[0] ) 저쩌구; 를 실행

만약 STL에서 int 같은 안정성보다 더 큰 안정성을 제공하면 v[0]v[1]의 lock을 동시에 잡아줘야 한다 – 그래야 비교하는 동안 값이 안변하니. 근데 T1에서는 0 1의 순서로 lock을 잡고, T2에서는 역순으로 잡는다. 뭔가 떠오르지 않는가? 락 순서 문제로 데드락; deadlock에 빠진다. 즉, STL에서 단순한 스레드 안전성 이상의 뭔가를 제공하려는 순간 두 가지 선택에 직면하게 된다.

  • 데드락의 위험을 사용자가 미리 알고 잘 대처하게 한다 – 라이브러리는 잘못 사용되기 힘들어야한다는 원칙에 위배
  • 더 복잡한 알고리즘을 써서 락 순서 문제를 _내부적으로 해결_한다 – 실제로 필요하지 않을 곳에도 불필요한 복잡성을 부여해서 _효율성을 추구하는 C++_의 목적을 방해

둘 다 문제가 있다. 그래서 C++의 STL은 아주 단순한 수준의 스레드 안전성을 보장해준다.

“STL은 스레드 안전성이 없다” 와 “STL은 이런 형태의 – 그러니까 int 같은 – 안전성을 제공한다” 라는 두 명제 사이의 차이는 엄청난 것이다. 무엇이 가능한지 잘 알고 프로그래밍하는 것이 좋겠다.


  1. 컴파일 시간에 타입에 따라 다른 기능을 갖게 할 수도 있기 때문에, 이런 기능을 사용해는 것을 컴파일 시간 다형성; compile time polymorphism 이라고 부르기도 한다. ↩︎

  2. 그렇다고 volatile int 에 사용할 수 있는 atomic operation을 사용한 수준의 안전성을 기대하진 말자. ↩︎