Computer >> 컴퓨터 >  >> 프로그래밍 >> C 프로그래밍

C 언어 멀티스레딩 완벽 가이드: POSIX 스레드 생성부터 종료까지

멀티스레딩(Multithreading)은 멀티태스킹(Multitasking)의 특수한 형태입니다. 멀티태스킹이란 컴퓨터가 두 개 이상의 프로그램을 동시에 실행할 수 있도록 해주는 기능을 말합니다. 일반적으로 멀티태스킹에는 프로세스 기반스레드 기반의 두 가지 유형이 있습니다.

프로세스 기반 멀티태스킹은 프로그램 자체의 동시 실행을 담당하고, 스레드 기반 멀티태스킹은 하나의 프로그램을 구성하는 여러 조각(작업 단위)의 동시 실행을 다룹니다.

멀티스레드 프로그램은 동시에 실행될 수 있는 두 개 이상의 부분으로 구성됩니다. 이때 프로그램의 각 부분을 스레드(thread)라고 부르며, 각 스레드는 서로 독립적인 실행 경로를 정의합니다.

C 언어와 멀티스레딩 지원

C 언어는 멀티스레드 애플리케이션을 위한 내장 지원 기능을 제공하지 않습니다. 대신 이 기능을 운영체제(OS)에 전적으로 의존하게 됩니다.

이 글에서는 Linux 환경에서 작업한다고 가정하고, POSIX를 사용해 멀티스레드 C 프로그램을 작성하는 방법을 살펴보겠습니다. POSIX 스레드(Pthreads)는 FreeBSD, NetBSD, GNU/Linux, macOS, Solaris 등 다양한 Unix 계열 POSIX 시스템에서 사용할 수 있는 API를 제공합니다.

POSIX 스레드 생성하기

POSIX 스레드를 생성할 때는 다음 루틴(routine)을 사용합니다.

#include <pthread.h>
pthread_create (thread, attr, start_routine, arg)

pthread_create 함수는 새로운 스레드를 생성하고 실행 가능한 상태로 만듭니다. 이 루틴은 코드 내 어디에서든 몇 번이고 호출할 수 있습니다. 각 매개변수의 의미는 아래 표와 같습니다.

매개변수설명
thread서브루틴이 반환하는 새 스레드의 고유 식별자(opaque identifier)입니다.
attr스레드 속성을 설정하는 데 사용되는 속성 객체입니다. 스레드 속성 객체를 지정하거나, 기본값을 사용하려면 NULL을 전달할 수 있습니다.
start_routine스레드가 생성된 후 실행할 C 루틴(함수)입니다.
argstart_routine에 전달할 단일 인자입니다. 반드시 void 타입 포인터로 캐스팅하여 참조 방식으로 전달해야 하며, 전달할 인자가 없다면 NULL을 사용할 수 있습니다.

하나의 프로세스가 생성할 수 있는 최대 스레드 수는 구현(implementation)에 따라 달라집니다. 한번 생성된 스레드들은 서로 대등한 관계(peer)이며, 다른 스레드를 추가로 생성할 수도 있습니다. 스레드 사이에 암묵적인 계층 구조나 종속 관계는 존재하지 않습니다.

스레드 종료하기

POSIX 스레드를 종료할 때는 다음 루틴을 사용합니다.

#include <pthread.h>
pthread_exit (status)

pthread_exit는 스레드를 명시적으로 종료할 때 사용됩니다. 일반적으로 스레드가 자신의 작업을 모두 마치고 더 이상 존재할 필요가 없을 때 호출됩니다.

만약 main() 함수가 자신이 생성한 스레드들보다 먼저 끝나더라도, main()이 pthread_exit()으로 종료된다면 나머지 스레드들은 계속해서 실행됩니다. 반대로 그렇지 않으면 main()이 종료되는 순간 다른 스레드들도 자동으로 강제 종료됩니다.

예제 코드

다음은 5개의 스레드를 생성하고 각각 "Hello World" 메시지를 출력하는 간단한 예제입니다.

#include <iostream>
#include <cstdlib>
#include <pthread.h>
using namespace std;
#define NUM_THREADS 5
void *PrintHello(void *threadid) {
    long tid;
    tid = (long)threadid;
    printf("Hello World! Thread ID, %d\n", tid);
    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) {
          printf("Error:unable to create thread, %d\n", rc);
          exit(-1);
       }
   }
   pthread_exit(NULL);
}

실행 결과

컴파일 시 -lpthread 옵션을 반드시 붙여야 pthread 라이브러리가 링크됩니다.

$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와 함께 메시지를 출력하는 것을 확인할 수 있습니다. 실제 실행 환경에 따라 스레드의 출력 순서는 달라질 수 있다는 점도 기억해두세요.