Posix Threads: 두 판 사이의 차이
| 3번째 줄: | 3번째 줄: | ||
==개요== | ==개요== | ||
Pthreads(Posix threads)는 C 프로그램에서 쓰레드(thread)를 조작하기 위한 표준적인 인터페이스이다. Pthreads는 프로그램이 쓰레드를 생성하고, 종료시키며, 수거할 수 있도록 하며, 또한 피어(peer) 쓰래드들과 데이터를 안전하게 공유하며, 시스템 상태 변화에 대해 피어들에게 알리도록 하는 약 60개 정도의 함수를 정의한다. 아래는 간단한 Pthreads 프로그램을 보여준다: | Pthreads(Posix threads)는 C 프로그램에서 쓰레드(thread)를 조작하기 위한 표준적인 인터페이스이다. Pthreads는 프로그램이 쓰레드를 생성하고, 종료시키며, 수거할 수 있도록 하며, 또한 피어(peer) 쓰래드들과 데이터를 안전하게 공유하며, 시스템 상태 변화에 대해 피어들에게 알리도록 하는 약 60개 정도의 함수를 정의한다. 아래는 간단한 Pthreads 프로그램을 보여준다: | ||
[[파일:Figure 1. Execution of Threaded “hello, world”.png|섬네일|Figure 1. Execution of Threaded “hello, world”]] | [[파일:Figure 1. Execution of Threaded “hello, world”.png|섬네일|Figure 1. Execution of Threaded “hello, world”|350x350픽셀]] | ||
<syntaxhighlight lang="c"> | <syntaxhighlight lang="c"> | ||
#include "csapp.h" | #include "csapp.h" | ||
2025년 5월 21일 (수) 06:04 판
상위 문서: Concurrent Programming
개요
Pthreads(Posix threads)는 C 프로그램에서 쓰레드(thread)를 조작하기 위한 표준적인 인터페이스이다. Pthreads는 프로그램이 쓰레드를 생성하고, 종료시키며, 수거할 수 있도록 하며, 또한 피어(peer) 쓰래드들과 데이터를 안전하게 공유하며, 시스템 상태 변화에 대해 피어들에게 알리도록 하는 약 60개 정도의 함수를 정의한다. 아래는 간단한 Pthreads 프로그램을 보여준다:

#include "csapp.h"
void *thread(void *vargp);
int main() {
pthread_t tid;
Pthread_create(&tid, NULL, thread, NULL);
Pthread_join(tid, NULL);
exit(0);
}
void *thread(void *vargp) { /* Thread routine */
printf("Hello, world!\n");
return NULL;
}
위에서 메인 쓰레드는 하나의 피어 쓰레드를 생성하고, 그 쓰레드가 종료할 때까지 기다린다. 피어 쓰레드는 "Hello, world!\n"을 출력하고 종료된다. 메인 쓰레드가 동료 쓰레드의 종료를 감지하면, exit을 호출하여 프로세스를 종료한다. 좀 더 자세히 살펴보면, 피어 쓰레드에서 사용되는 코드와 로컬 데이터는 쓰레드 루틴 내[1]에 캡슐화되어 있다. 2번째 줄에 있는 프로토타입이 보여주듯이, 각 쓰레드 루틴은 하나의 일반 포인터(generic pointer)를 입력으로 받고, 일반 포인터를 반환한다. 만약 여러 개의 인자를 쓰레드 루틴에 전달하고자 한다면, 인자들을 구조체에 넣고 그 구조체에 대한 포인터를 전달하면 된다. 마찬가지로, 쓰레드 루틴이 여러 값을 반환하게 만들고자 할 때에도 구조체에 대한 포인터를 반환하면 된다.
main 함수는 메인 쓰레드의 코드가 시작되는 부분을 포함한다. 메인 쓰레드는 지역 변수 tid를 선언하며, 이는 피어 쓰레드의 TID(Thread ID)를 저장하는데 사용된다. 그리고 메인 쓰레드는 pthread_create() 함수를 호출하여 새로운 피어 쓰레드를 호출한다. 해당 함수의 호출이 리턴되면, 메인 쓰레드와 새롭게 생성된 피어 쓰레드는 동시에 실행되며, tid에는 피어 프로세스의 TID가 저장된다. 그리고 메인 쓰레드는 pthread_join() 함수를 호출하여 피어 쓰레드가 종료될 때까지 기다린다. 마지막으로, 메인 쓰레드는 exit()을 호출하여 프로세스를 종료한다.
Creating Threads
쓰레드는 pthread_create() 함수를 호출하여 다른 쓰레드를 생성한다:
#include <pthread.h>
typedef void *(func)(void *);
//반환값: 성공 시 0, 오류시 0이 아닌 값
int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
pthread_create() 함수는 새로운 쓰레드를 생성하고, 새 쓰레드의 컨텍스트(context)에서 쓰레드 루틴을 함수 포인터 f와 해당 함수의 인자를 저장하는 일반 포인터 arg를 통해 생성한다. 또한 attr 인자는 새로 생성된 쓰레드의 기본 속성을 변경하는데 사용되며, 기본적으로는 NULL을 사용한다. pthread_create()가 반환되면, 인자인 tid에는 새로 생성된 쓰레드의 TID가 저장된다. 이때 각 쓰레드는 아래와 같이 pthread_self 함수를 호출하여 자신의 TID를 알아낼 수 있다.
#include <pthread.h>
//반환값: 호출자의 TID
pthread_t pthread_self(void);
Terminating Threads
쓰레드는 다음 중 하나의 방식으로 종료된다:
- 최상위 쓰레드 루틴이 반환될 때, 암묵적으로 해당 쓰레드는 종료된다.[2]
- 최상위 쓰레드 루틴이 반환된다는 것은 쓰레드가 처음 실행을 시작할 때 지정된 함수가 리턴을 통해 실행을 마친다는 뜻이다.
pthread_exit()함수를 호출하여 명시적으로 종료된다.- thread_return 인자는 쓰레드가 종료될 때
pthread_join()함수를 통해 기다리고 있는 쓰렏드에게 반환하고자 하는 값을 전달하는 포인터이다. - 메인 쓰레드가
pthread_exit()함수를 호출하면, 다른 피어 쓰레드들이 종료될 때까지 기다린 후, 메인 쓰레드와 전체 프로세스를 thread_return 값과 함께 종료시킨다.
- thread_return 인자는 쓰레드가 종료될 때
#include <pthread.h>
void pthread_exit(void *thread_return);
- 어떤 피어 쓰레드가
exit()함수를 호출하면, 해당 프로세스와 그에 속한 모든 쓰레드가 종료된다. - 다른 피어 쓰레드가
pthread_cancel()함수를 호출하면서 어떤 쓰레드의 TID를 인자로 전달하면, 해당 쓰레드가 종료된다.
#include <pthread.h>
//반환값: 성공 시 0, 실패 시 0이 아닌 값
int pthread_cancel(pthread_t tid);
Reaping Terminated Threads
쓰레드는 pthread_join() 함수를 호출하여 다른 쓰레드가 종료될 때까지 기다린다:
#include <pthread.h>
//반환값: 성공 시 0, 오류 시 0이 아닌 값
int pthread_join(pthread_t tid, void **thread_return);
pthread_join() 함수는 tid에 해당하는 쓰레드가 종료될 때까지 해당 함수를 호출한 쓰레드를 블록(block)시키며, tid 쓰레드 루틴이 반환한 일반 포인터(void *) 값을 thread_return이 가리키는 위치에 할당하고, 종료된 쓰레드가 점유하고 있던 메모리 자원들을 수거한다. 이때 리눅스의 wait() 함수와는 달리, pthread_join() 함수는 특정한 쓰레드의 종료를 기다리는 작업만 할 수 있다.
Detaching Threads
쓰레드는 항상 조인 가능(joinable)하거나 분리(detatched) 상태이다. 조인 가능한 쓰레드는 다른 쓰레드에 의해서 수거되고 종료될 수 있으며, 해당 쓰레드의 스택과 같은 자원은 다른 쓰레드에 의해서 수거되기 전까지는 해제되지 않는다. 반면, 분리된 쓰레드는 다른 쓰레드에 의해 수거되거나 종료될 수 없으며, 해당 쓰레드가 종료되면 메모리 자원은 시스템에 의해서 자동으로 해제된다.
기본적으로 쓰레드는 조인 가능한 상태로 생성되며, 메모리 누수를 방지하기 위해서는 조인 가능한 쓰레드를 명시적으로 다른 쓰레드에 의해서 수거되도록 하거나, pthread_detach() 함수를 호출하여 분리된 쓰레드로 변경해야 한다:
#include <pthread.h>
//반환값: 성공 시 0, 오류 시 0이 아닌 값
int pthread_detach(pthread_t tid);
pthread_detach() 함수는 조인 가능한 쓰레드 tid를 분리된 쓰레드로 만든다. 이때 쓰레드는 pthread_self() 함수의 반환값을 인자로 하여 pthread_detach() 함수를 통해 자신의 상태를 변경할 수도 있다.
Initializing Threads
pthread_once() 함수는 쓰레드 루틴과 관련된 상태를 초기화할 수 있도록 한다:
#include <pthread.h>
pthread_once_t once_control = PTHREAD_ONCE_INIT;
//항상 0을 반환
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
once_control 변수는 전역(global)이나 정적(static)으로 선언되며, 항상 PTHREAD_ONCE_INIT으로 초기화된다. once_control을 인자로 하여 pthread_once() 함수를 처음 호출하면, 어떤 초기화 함수인 init_routine() 함수가 호출된다. 이후 동일한 once_control 변수로 pthread_once() 함수를 다시 호출하면 아무 일도 일어나지 않는다. pthread_once() 함수는 여러 쓰레드가 초기화 작업을 동시에 실행하더라도, 특정 초기화 함수가 단 한번만 실행되도록 보장한다. 따라서, 여러 쓰레드가 공유하는 전역 변수를 동적으로 초기화해야 할 때 유용하다.