멀티 쓰레드 환경에서 fork는 조심해야 한다.

리눅스나 유닉스 같은 POSIX 시스템에서는 fork를 이용해서 자신과 똑같은 프로세스를 만들 수 있다. 이때 fork를 호출한 프로세스를 부모 프로세스라고 하고, 새로 생성된 프로세스를 자식 프로세스라고 부르는데, 자식 프로세스는 부모 프로세스의 모든 메모리를 복사한다.

fork는 그 뒤 exec을 해서 다른 바이너리를 실행시키는 fork-exec이 일반적인 사용법이다. 하지만 자식 프로세스부모 프로세스와 완전히 같은 메모리를 가지기 때문에, 스레드가 존재하지 않던 시절에는 exec을 하지 않고 병렬 처리를 하기 위해서도 자주 사용되었다. 지금은 스레드를 사용하는 것이 더 사용하기 쉽고 가벼운 방식이기에 스레드를 병렬처리를 위해 스레드를 주로 사용하지만, 스레드보다 서로 간에 독립적이기 때문에 일을 분리하기 위해서 사용하기도 한다.

분명히 fork는 없어서는 안 될 기능이지만 fork에는 태생적 한계가 있다. 애초에 fork는 스레드라는 개념이 존재하지 않던 시절에 만들어졌기 때문에 thread-safe 하지 않다. 따라서 멀티스레드 환경에서 사용하려면 조심해서 사용해야 한다.

fork가 멀티스레드 환경에서 문제를 일으키는 이유는 fork부모 프로세스의 메모리를 전부 복사하지만, fork를 호출한 스레드를 제외한 나머지 스레드들은 죽어버리기 때문이다. 따라서 fork를 호출한 스레드 이외의 스레드에서 획득한 자원은 아무것도 해제되지 않는다. 메모리 릭도 충분히 문제지만, 이는 단순한 메모리 릭만을 의미하지 않는다.

fork가 복사한 메모리에는 힙이나 스택 이외에 mutexcondition variable 들도 포함된다. 만약 mutexcondition variable이 다른 스레드에서 사용된 채로 fork 된다면 해당 변수로 보호되는 critical section에는 다시는 진입할 수 없게 된다. 특히나 malloc 같은 thread-safe 한 함수는 대부분 내부적으로 글로벌한 mutex를 사용하기 때문에 내 코드에 mutex가 없어도 안심할 수 없다. 따라서 멀티스레드 환경에서는 fork 하기 전에 다른 스레드가 전부 멈추는 것을 확인하는 것이 좋다.

하지만 fork 후 바로 exec 하면 이런 것을 신경 쓸 필요 없다. exec의 경우 메모리를 완전히 새로 만들기 때문에 이런 문제가 발생하지 않는다.

댓글

댓글 쓰기

이 블로그의 인기 게시물

[C++] enum class - 안전하고 쓰기 쉬운 enum

RAII는 무엇인가

Log Aggregator 비교 - Scribe, Flume, Fluentd, logstash

[Python] cache 데코레이터로 최적화하기

[Web] SpeechSynthesis - TTS API