멀티스레딩과 STL

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

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

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


간단히 설명하면 이렇다. 우선 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에서는 역순으로 잡는다. 뭔가 떠오르지 않는가? 예전에 설명했던 lock ordering 문제로 데드락; deadlock에 빠진다. 즉, STL에서 단순한 스레드 안전성 이상의 뭔가를 제공하려는 순간 두 가지 선택에 직면하게 된다.

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

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

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

여담이지만 학부시절에 동기 중에 이정도도 모르는 사람이 있었는데 회사 와서는 아직 그런 사람을 찾지 못해서 무척 행복하다.

*

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

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

ps. 안전성을 자꾸 으로 쓰는 저주받은 손 때문에 글 쓰는 내내 괴로웠다(…)

Published by

rein

나는 ...

5 thoughts on “멀티스레딩과 STL”

  1. 아하, 이런 내막이 있었군요. 그저 “민감한 작업만 안하면 된다. 민감한 작업을 하는 클래스를 컨테이너에 담을 거면 그 클래스 단에서 스레드 안정성 체크만 해주면 된다.” 정도로만 알고 있었는데 무적의 군주사마 덕분에 제 지식을 업데이트할 수 있었습니다(?)

  2. 뭐 사실 따지고 보면 원론적인 얘기지. 일반적인 int 에 적용되는대로만 생각하면 된다랄까;

  3. “STL은 스레드 안전성이 없다” 와 “STL은 이런 형태의 – 그러니까 int 같은 – 안전성을 제공한다” 라는 두 명제 사이의 차이는 엄청난 것이다. 무엇이 가능한지 잘 알고 프로그래밍하는 것이 좋겠다. 라는 글의 의미는 정확히 이해하고 있습니다. 즉 수치로 표현 하자면 안정성을 제공한다 – 10, 안정성이 없다 – 0 의 차이인데, int 라는 것이 정말 쓰레드 안전성이 있는 걸까요? 정수도 원자적인 접근이 안되는 상황에서 그게 안정성을 제공한다라는 것 자체에 저는 의문은 가집니다. 보는 관점이 차이일수도 있겠지만 저는 그건 안정성을 그냥 0 이라고 봅니다.

    1. int에 대한 스레드 안정성에 관해 얘기하려면, C++ 메모리 모델을 가지고 말해야하는데, 이건 명세가 없지요(플랫폼 의존적이니).
      안정성을 0이라고 보기에는 약간 다르지 않을까요. 한 스레드만 접근하거나(TLS), 읽기만 하는 경우에는 안전(?)하니까요; 뭐 그 정도의 맥락으로 받아들여주셨으면 합니다.

      ps. 한동안 블로그를 방치해둬서(…) 댓글 확인이 늦었습니다.

  4. 답변 감사드립니다.

    음 결국 관점과 플랫폼 구현의 차이인가요?

    또는 안정성의 의미에 대한 범위 차원의 차이인가요?

    그냥 저는 일반적인 상황

    – 특별한 수식이 없는 전역 변수, 객체의 멤버 변수 등에 최소한 두개의 쓰레드가 동시에 읽고/쓰는 경우

    을 예로 했을 때, 기본적으로 동기화(lock)가 필요해서 안정성이 없다고 생각했습니다.

    저는 쓰레드 안정성을 이 일반적인(?) 경우에 동기화(lock)가 필요없이 여러 쓰레드에서 읽고 쓰기가 보장될 경우에 안정성이 있다고 봤습니다.

    특수한 경우, 즉, 여러 쓰레드가 읽기만 가능할 경우나, TLS의 경우는 동시에 읽고 쓰는 상황이 안되므로 동기화할 필요가 없어서 안정성을 제공하지 않는 것이나 다름없다라고 보는 것이구요~

    특수한 경우에는 당연히 안정하므로 광범위한 관점에서 안정성을 제공하느냐 마느냐의 차원에서 보면 그렇게 볼 수 있겠습니다. ^^

Leave a Reply