스레드는 주로 프로그램 성능을 향상시키기 위해 사용한다. 스레드는 낮은 운영 체제 오버헤드와 적은 시스템 자원으로 작성 및 관리할 수 있다. 프로세스 내의 모든 스레드는 동일한 주소 공간을 공유하므로 프로세스 간 통신보다 더 쉽고 효율적으로 스레드 간 통신을 구현할 수 있다. 예를 들어, 한 스레드가 입/출력(I/O) 시스템 호출이 완료되기를 기다리고 있을 때 다른 스레드가 CPU 자원을 많이 사용하는 태스크를 수행할 수 있다. 스레드를 사용하면 중요한 태스크에 우선순위를 부여할 수 있으며 우선순위가 낮은 태스크를 인터럽트할 수도 있다. 가끔씩 발생하는 태스크를 정기적으로 스케줄된 태스크 사이에 배정하여 스케줄을 유연하게 조정할 수 있다. 마지막으로 pthread는 다중 CPU 시스템의 병렬 프로그래밍에 이상적이다.
POSIX 스레드 또는 pthread를 사용하는 주된 이유는 훨씬 더 단순하다. 그 이유는 바로 pthread가 표준화된 C 언어 스레드 프로그래밍 인터페이스의 일부로서 이식성이 매우 높기 때문이다.
POSIX 스레드 프로그래밍에는 여러 장점이 있지만 몇 가지 기본 규칙을 명확히 알지 못하면 디버그하기가 어려운 코드를 작성하고 메모리 누수가 발생할 수 있는 위험이 있다. 먼저 결합 가능한 스레드 또는 분리된 스레드일 수 있는 POSIX 스레드부터 검토해 보자.
새 스레드를 작성하려면 스레드의 종료 방법을 알아야 하고 결합 가능한 스레드가
필요하다. 결합 가능한 스레드의 경우, 시스템이 스레드 종료 상태를 저장할 수 있는 개인용 저장소를
할당한다. 상태는 스레드 종료 후 업데이트된다. 스레드 종료 상태를 검색하려면 pthread_join(pthread_t thread, void** value_ptr)을
호출한다.
시스템이 스택, 스레드 ID, 스레드 종료 상태 등이 포함된 각 스레드에 대한 기본 저장소를 할당한다. 이 기본 저장소는 스레드가 종료되고 다른 스레드에 결합될 때까지 프로세스 공간에 유지된다(재활용되지 않음).
대부분의 경우에는 스레드를 작성한 후 일부 태스크를 스레드에 지정한 다음 계속해서 다른 작업을 처리한다. 이러한 경우에는 스레드의 종료 방법을 신경 쓰지 않아도 되므로 분리된 스레드를 사용하는 것이 좋다.
분리된 스레드의 경우에는 시스템이 스레드 종료 후 자동으로 기본 자원을 재활용한다.
결합 가능한 스레드를 작성했지만 결합하는 것을 잊어버린 경우 해당 자원 또는 개인용 메모리는 회수되지 않은 채로 프로세스 공간에 계속 유지된다. 결합 가능한 스레드는 항상 결합해야 한다. 그렇지 않으면 심각한 메모리 누수가 발생할 수 있다.
예를 들어, RHEL4(Red Hat Enterprise Linux)의 스레드에는 10MB 스택이 필요하다.
이는 곧 스레드를 결합하지 않았을 경우 적어도 10MB가 누수된다는 것을 의미한다. 수신 요청을
처리하기 위한 관리자-작업자 모드 프로그램을 설계한다고 가정해 보자. 이 프로그램에서는 작성된
후 개별 태스크를 수행하고 종료되는 많은 작업자 스레드가 필요하다. 이러한 스레드가 결합 가능한
스레드일 경우 pthread_join()을 호출하여 스레드를 결합하지 않으면
작성된 각 스레드에서 종료 후 상당한 용량의 메모리가 누수된다(적어도 스택당 10MB). 누수된 메모리의
크기는 더 많은 작업자 스레드가 작성된 후 결합되지 않고 종료될수록 증가한다. 게다가 새 스레드를
작성하는 데 사용할 수 있는 메모리가 없으면 프로세스에서 새 스레드를 작성할 수 없게 된다.
Listing 1에서는 결합 가능한 스레드의 결합을 잊어버린 경우 작성되는 심각한 메모리 누수를 보여 준다. 또한 이 코드를 사용하여 한 프로세스 공간에서 공존할 수 있는 스레드 본체의 최대 수를 확인할 수도 있다.
Listing 1. 메모리 누수 작성하기
#include<stdio.h>
#include<pthread.h>
void run() {
pthread_exit(0);
}
int main () {
pthread_t thread;
int rc;
long count = 0;
while(1) {
if(rc = pthread_create(&thread, 0, run, 0) ) {
printf("ERROR, rc is %d, so far %ld threads created\n", rc, count);
perror("Fail:");
return -1;
}
count++;
}
return 0;
}
|
Listing 1에서는 pthread_create()를 호출하여 기본
스레드 속성을 지닌 새 스레드를 작성한다. 기본적으로 새로 작성되는 스레드는 결합 가능한 스레드이다. 이
코드는 실패가 발생할 때까지 결합 가능한 새 스레드를 계속 작성한다. 그런 다음 오류 코드와 실패 이유를 출력한다.
Red Hat Enterprise Linux Server 릴리스 5.4에서 [root@server ~]# cc -lpthread thread.c -o thread
명령을 사용하여 Listing 1의 코드를 컴파일하면 Listing 2와 같은 결과가 표시된다.
Listing 2. 메모리 누수 결과
[root@server ~]# ./thread ERROR, rc is 12, so far 304 threads created Fail:: Cannot allocate memory |
이 코드를 실행하면 304개의 스레드가 작성된 후 더 이상 작성되지 않는다. 오류 코드는
12이며, 이는 메모리가 없음을 의미한다.
Listing 1 및 2에서 살펴본 대로 결합 가능한 스레드를 생성한 후 결합하지 않으면 종료된 결합 가능한 각 스레드가 프로세스 공간을 계속 차지하고 있기 때문에 프로세스 메모리가 누수된다.
RHEL의 POSIX 스레드는 10MB 크기의 개인용 스택을 사용한다. 다시 말해서 시스템이 적어도 10MB의 개인용 저장소를 각 pthread에 할당한다. 이 기사의 예제에서는 프로세스 종료 전까지 304개의 스레드가 작성되었으며, 이러한 스레드는 304*10MB 즉, 약 3GB의 메모리를 차지한다. 한 프로세스의 가상 메모리 크기는 4GB이며, 프로세스 공간의 1/4은 Linux 커널용으로 예약되어 있다. 결론적으로 3GB 메모리 공간을 사용자 공간으로 사용할 수 있다. 따라서 3GB 메모리가 죽은 스레드에 의해 사용되며, 이는 심각한 메모리 누수이다. 그리고 이러한 메모리 누수가 얼마나 빨리 발생하는지 쉽게 알 수 있다.
결합 가능한 각 스레드를 결합하는 pthread_join()을
호출하는 코드를 추가하여 누수를 해결할 수 있다.
다른 메모리 누수와 마찬가지로 프로세스가 시작될 때는 문제점이 명확히 나타나지 않을 수 있다. 따라서 여기에서는 소스 코드에 액세스하지 않고도 그러한 문제점을 발견할 수 있는 방법을 설명한다.
- 프로세스의 스레드 스택 수를 계산한다. 이 수에는 실행 중인 활성 스레드와 종료된 스레드의 수가 포함된다.
- 프로세스의 실행 중인 활성 스레드 수를 계산한다.
- 두 수를 비교한다. 기존 스레드 스택 수가 실행 중인 활성 스레드 수보다 크고 프로그램이 계속 실행되는 동안 이러한 두 수의 차이가 커지면 메모리 누수가 발생하고 있는 것이다.
그리고 대부분의 경우 이러한 메모리 누수는 결합 가능한 스레드를 결합하지 않았을 때 발생한다.
실행 중인 프로세스에서 스레드 스택의 수는 프로세스에 있는 스레드 본체의 수와 같다. 스레드 본체는 실행 중인 활성 스레드와 결합 가능한 죽은 스레드로 구성되어 있다.
pmap은 프로세스 메모리에 대한 정보를 보고하는 데
사용되는 Linux 도구이다. 다음 명령을 결합하여 스레드 스택 수를 확인할 수 있다.
[root@server ~]# pmap PID | grep 10240 | wc
-l
(10240KB는 Red Hat Enterprise Linux Server 릴리스 5.4의 기본 스택 크기이다.)
/proc/PID/task를 사용하여 활성 스레드 수 계산하기
스레드가 작성되어 실행될 때마다 /proc/PID/task에 한 항목이 추가된다. 스레드가 종료되면 결합 가능한 스레드이거나 분리된 스레드이거나 상관 없이 /proc/PID/task에서 해당 항목이 제거된다. 따라서 다음 명령을 실행하여 활성 스레드 수를 확인할 수 있다.
[root@server ~]# ls /proc/PID/task | wc -l.
pmap PID | grep 10240 | wc -l의 출력을 검사한 후
ls /proc/PID/task | wc -l의 출력과 비교한다. 모든 스레드 스택 수가
활성 스레드 수보다 크고 프로그램이 계속 실행되는 동안 두 수의 차이가 커지면 누수 문제점이 있는
것으로 판단할 수 있다.
결합 가능한 스레드는 프로그래밍 동안 결합되어야 한다. 프로그램에서 결합 가능한
스레드를 작성할 때 pthread_join(pthread_t, void**)을 호출하여 스레드에
할당된 개인용 저장소를 재활용하는 것을 잊지 말아야 한다. 그렇지 않으면 심각한 메모리 누수가 발생한다.
프로그래밍 이후 테스트 단계 동안 pmap과
/proc/PID/task를 사용하여 그러한 누수가 있는지 여부를 확인할
수 있다. 누수가 있으면 소스 코드를 검사하여 모든 결합 가능한 스레드가 결합되었는지 확인한다.
이렇게만 하면 된다. 간단한 예방 작업을 수행하면 나중에 추가 작업을 하지 않아도 되고 메모리 누수도 방지할 수 있다.
교육
- "/proc
파일 시스템을 활용한 리눅스 커널 접근"(developerWorks, 2006년 3월)에서는 이 가상 파일 시스템의 고유 통신 기능을 활용하는
방법을 보여 준다.
- Terrehon Bowden 및 Bodo Bauer의
/proc filesystem manual도 읽어보자.
- pthread에 대한 자세히 알고 싶다면 Mark Hays의
POSIX Threads Tutorial을 읽어보자.
이 튜토리얼에서는 POSIX 스레드를 사용하여 병렬 애플리케이션을 작성하는 방법을 보여 준다.
pmap에 대한 linuxmanpages 섹션에서는 이 맵핑 명령을 사용하는 데 필요한 모든 정보를 제공한다.- 리눅스에서는
수백 개의 기술자료 목록과 함께, Linux 개발자와 관리자를 위한
다양한 다운로드, 토론 포럼 및 다른 참고자료를 찾을 수 있다.
- developerWorks 기술 행사 및 웹 캐스트를 통해 다양한 IBM 제품 및 IT 업계의 주제에 관한 최신 정보를 볼 수 있다.
- 무료 developerWorks Live!
briefing을 통해 최신 IBM 제품 및 도구에 대한 정보뿐만 아니라 IT 업계의 최신 경향까지도 빠르게 확인할 수 있다.
- developerWorks
on-demand demos에서는 입문자를 위한 제품 설치 및 설정부터 숙련된 개발자를 위한 고급 기능까지 망라된 다양한 데모를 제공한다.
- Twitter의 developerWorks를 팔로우(follow)하거나
developerWorks에 대한 Linux 트윗(tweet)의 피드를 구독하자.
제품 및 기술 얻기
-
자신에게 가장 적합한 방법으로 IBM 제품을 평가해 보자.
시험판 제품을 다운로드하거나 온라인으로 제품을 사용해 보거나 클라우드 환경에서 제품을 사용하거나
SOA Sandbox에서
SOA(Service Oriented Architecture)를 효과적으로 구현하는 방법을 배울 수 있다.
토론
- My developerWorks 커뮤니티에 참여하자.
개발자 중심 블로그, 포럼, 그룹 및 Wiki 검색 중에 다른 developerWorks
사용자와 의견을 교환해 보자.

For the past 3 years, Wei Dong has worked as a Product Engineer for IBM Systems Director with the responsibility of fixing issues reported by customers. Before he joined IBM, Wei Dong did a 10-month internship at Intel as a Linux developer. In 2007, he graduated from Nanjing University, China, with an MS degree.