멀티스레딩이란?
멀티스레딩(Multithreading)은 멀티태스킹의 한 특수 형태입니다. 여기서 멀티태스킹이란 컴퓨터가 두 개 이상의 프로그램을 동시에 실행할 수 있도록 해주는 기능을 의미합니다. 일반적으로 멀티태스킹에는 두 가지 유형이 있습니다. 바로 프로세스 기반(process-based) 방식과 스레드 기반(thread-based) 방식입니다.
프로세스 기반 멀티태스킹은 프로그램 전체의 동시 실행을 처리하는 반면, 스레드 기반 멀티태스킹은 동일한 프로그램 내부의 일부 조각들을 동시에 실행하는 것을 다룹니다.
멀티스레드 프로그램은 동시에 실행될 수 있는 두 개 이상의 부분으로 구성됩니다. 이러한 프로그램의 각 부분을 스레드(thread)라고 부르며, 각 스레드는 하나의 독립적인 실행 경로를 정의합니다.
C++과 멀티스레딩
C++ 언어 자체에는 멀티스레드 애플리케이션을 위한 내장 지원이 포함되어 있지 않습니다. 대신 이 기능은 전적으로 운영체제(OS)에 의존하게 됩니다.
이 튜토리얼에서는 Linux 운영체제 환경에서 작업한다고 가정하고, POSIX를 활용해 멀티스레드 C++ 프로그램을 작성하는 방법을 살펴보겠습니다. POSIX 스레드(Pthreads)는 FreeBSD, NetBSD, GNU/Linux, Mac OS X, Solaris 등 다양한 Unix 계열 POSIX 시스템에서 사용할 수 있는 API를 제공합니다.
스레드 생성하기
POSIX 스레드를 생성하려면 다음 루틴을 사용합니다.
#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)
여기서 pthread_create는 새로운 스레드를 생성하고 실행 가능한 상태로 만듭니다. 이 루틴은 코드 내 어느 위치에서든 원하는 만큼 여러 번 호출할 수 있습니다. 각 매개변수에 대한 설명은 아래와 같습니다.
| 매개변수 | 설명 |
|---|---|
| thread | 서브루틴이 반환하는 새 스레드의 고유 식별자(opaque identifier)입니다. |
| attr | 스레드 속성을 설정하는 데 사용할 수 있는 불투명한(opaque) 속성 객체입니다. 스레드 속성 객체를 직접 지정하거나, 기본값을 사용하기 위해 NULL을 전달할 수 있습니다. |
| start_routine | 스레드가 생성된 후 실행할 C++ 루틴입니다. |
| arg | start_routine에 전달할 단일 인수입니다. void 타입 포인터로 캐스팅하여 참조 형태로 전달해야 하며, 전달할 인수가 없다면 NULL을 사용할 수 있습니다. |
하나의 프로세스가 생성할 수 있는 최대 스레드 수는 구현 환경에 따라 달라집니다. 스레드가 한번 생성되면 서로 대등한 관계(peer)가 되며, 각 스레드는 또 다른 스레드를 생성할 수도 있습니다. 즉, 스레드 사이에는 별도의 암묵적인 계층 구조나 종속 관계가 존재하지 않습니다.
스레드 종료하기
POSIX 스레드를 종료하려면 다음 루틴을 사용합니다.
#include <pthread.h>
pthread_exit (status)
여기서 pthread_exit는 스레드를 명시적으로 종료하는 데 사용됩니다. 일반적으로 pthread_exit() 루틴은 스레드가 자신의 작업을 모두 마치고 더 이상 존재할 필요가 없을 때 호출됩니다.
만약 main() 함수가 자신이 생성한 스레드들보다 먼저 종료되더라도 pthread_exit()를 호출하여 종료하면, 나머지 스레드들은 계속해서 실행됩니다. 반대로 main()이 pthread_exit() 없이 그냥 끝난다면, 생성된 모든 스레드는 자동으로 종료됩니다.
예제 코드
다음은 5개의 스레드를 생성하고, 각 스레드가 "Hello World!" 메시지와 자신의 스레드 ID를 출력하는 간단한 예제입니다.
#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
long tid;
tid = (long)threadid;
cout << "Hello World! Thread ID, " << tid << endl;
pthread_exit(NULL);
}
int main () {
pthread_t threads[NUM_THREADS];
int rc;
int i;
for( i = 0; i < NUM_THREADS; i++ ) {
cout << "main() : creating thread, " << i << endl;
rc = pthread_create(&threads[i], NULL, PrintHello, (void *)i);
if (rc) {
cout << "Error:unable to create thread," << rc << endl;
exit(-1);
}
}
pthread_exit(NULL);
}
컴파일 및 실행 결과
위 코드는 컴파일 시 -lpthread 옵션을 반드시 붙여야 합니다.
$gcc test.cpp -lpthread
$./a.out
main() : creating thread, 0
main() : creating thread, 1
main() : creating thread, 2
main() : creating thread, 3
main() : creating thread, 4
Hello World! Thread ID, 0
Hello World! Thread ID, 1
Hello World! Thread ID, 2
Hello World! Thread ID, 3
Hello World! Thread ID, 4
실행 결과를 보면 main()이 스레드를 순차적으로 생성한 뒤, 각 스레드가 자신의 ID와 함께 메시지를 출력하는 것을 확인할 수 있습니다. 실제 실행 환경에 따라 출력 순서는 달라질 수 있다는 점도 참고하세요.