[Linux] POSIX 쓰레드 자료형과 API

mommers·2026년 2월 5일

Linux

목록 보기
47/59


1. 주요 자료형 (Data Types)

<pthread.h>에 정의된 불투명(Opaque) 구조체들입니다. 직접 멤버 변수에 접근하지 말고 전용 함수를 써야 합니다.

자료형역할실무 팁
pthread_t쓰레드 식별자 (ID)int가 아닐 수 있으므로 비교 시 == 대신 pthread_equal() 사용 권장.
pthread_mutex_t뮤텍스 (잠금장치)공유 자원 보호용. LOCK -> CRITICAL SECTION -> UNLOCK
pthread_cond_t조건 변수"신호(Signal)"를 기다릴 때 사용. 항상 뮤텍스와 짝으로 다님.
pthread_attr_t속성 객체스택 크기나 Detach 여부를 설정하고 create 할 때 넘겨줌.
pthread_once_t1회 초기화싱글톤 패턴이나 라이브러리 초기화에 사용 (PTHREAD_ONCE_INIT 필요).

2. 주요 API (핵심 함수)

필수 헤더: #include <pthread.h> (sched.h는 스케줄링 정책 정의용)

A. 쓰레드 제어

함수설명비유
pthread_create쓰레드 생성직원 고용. (성공 시 0 반환).
pthread_join종료 대기 (Blocking)직원이 퇴근할 때까지 문 앞에서 기다림. (자원 회수 필수).
pthread_detach독립 실행 (Non-blocking)"알아서 하고 퇴근해." (종료 시 자동 자원 회수). join 불가.
pthread_exit스스로 종료직원이 "저 먼저 갑니다" 하고 나감.
pthread_self내 ID 확인"내 사원증 번호가 뭐지?"

B. 동기화 (뮤텍스)

뮤텍스란 동시에 한 스레드만 특정 코드 영역에 들어가게 만드는 공유 자원을 보호하기 위한 도구입니다.
코드 영역에 접근하는 것을 잠구거나 풀어서 공유 자원을 보호하는 역할을 합니다.

  • pthread_mutex_init / destroy: 생성 및 소멸
  • pthread_mutex_lock / unlock: 잠그기 및 풀기

3. 쓰레드 기본 속성 (pthread_attr_init)

pthread_create의 두 번째 인자로 NULL을 주면 아래 기본값들이 적용됩니다.

속성 (Attribute)기본값 (Default)설명
Detach StatePTHREAD_CREATE_JOINABLE[중요] 기본적으로 join을 해줘야 메모리가 반환됨.
ScopePTHREAD_SCOPE_SYSTEM(리눅스 NPTL 기준) 커널이 1:1로 스케줄링함.
Inherit SchedPTHREAD_INHERIT_SCHED부모(생성한 쓰레드)의 우선순위를 그대로 물려받음.
Sched PolicySCHED_OTHER일반적인 시분할(Time-sharing) 스케줄링 (비실시간).
Stack Size시스템 기본값 (약 2MB~8MB)임베디드에서는 메모리 아끼려고 이 값을 줄여서(attr 설정) 생성함.

참고: 리눅스(NPTL)는 PTHREAD_SCOPE_PROCESS를 지원하지 않습니다. (항상 커널 레벨 스케줄링).


4. 실전 요약 코드 (Skeleton)

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// 쓰레드가 실행할 함수
void* worker_routine(void* arg) {
    int id = *(int*)arg;
    printf("[Thread] 일하는 중... (ID: %lu)\n", pthread_self());
    return NULL;
}

int main() {
    pthread_t thread;
    int arg = 100;
    
    // 1. 쓰레드 생성 (기본 속성 NULL 사용)
    if (pthread_create(&thread, NULL, worker_routine, &arg) != 0) {
        perror("Create failed");
        return 1;
    }

    // 2. 메인 쓰레드는 기다림 (Joinable 상태이므로 필수)
    pthread_join(thread, NULL);
    
    printf("[Main] 쓰레드가 종료되었습니다.\n");
    return 0;
}

5. 컴파일 주의사항

Pthreads는 표준 라이브러리(libc)에 포함되지 않는 경우가 많아, 링크 옵션을 꼭! 줘야 합니다.

gcc -o my_app my_app.c -pthread
# 또는
gcc -o my_app my_app.c -lpthread

6. 예제 : 쓰레드 종료 대기(pthread_join) 관련

함수설명비유
pthread_join종료 대기 (Blocking)직원이 퇴근할 때까지 문 앞에서 기다림. (자원 회수 필수).

pthread_join 함수는 목차 2번 에서 봤던 주요 api 핵심 함수 중 하나이다.

  • join을 하지 않으면 메인 스레드가 먼저 종료될 수 있다.
  • join을 통해 자식 스레드의 반환값을 받을 수 있다.

아래 예제를 통해 스레드의 종료 대기 및 자원 회수 메커니즘에 대해 알아보자.

코드

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#include <string.h>
#include <stdint.h>

//#define _GNU_SOURCE         /* See feature_test_macros(7) */
#include <unistd.h>
#include <sys/syscall.h>

void *thread_function(void *arg);

int main() {
	int status;
	pthread_t tid;
	void *return_value;
	int i;

	
	status = pthread_create(&tid, NULL, thread_function, "hello thread\n");
	if(status !=0){
		perror("pthread_create");
		exit(1);
	}
	
	for(i=1; i<=5; i++){
		printf("Parent thread %d!!\n", i);
		sleep(1);
	}

	//
	status = pthread_join(tid, &return_value);
	if(status != 0){
		perror("pthread_join");
		exit(1);
	}
	printf("Thread joined, it returned %s\n", (char *)return_value); 
	// printf("Thread joined, it returned %ld\n", (uintptr_t)return_value); 
	
	return 0;
}

void *thread_function(void *arg){
	int i;
	pid_t * tpid;
	pthread_t * thread_id;
	
	tpid=malloc(sizeof(pid_t));
	thread_id=malloc(sizeof(pthread_t));

	*tpid = syscall(SYS_gettid);
	printf("Thread LWP: %d, Thread PID: %d\n", *tpid, getpid());
	
	*thread_id = pthread_self();
	printf("Thread ID: %lu\n", *thread_id);
	
	for(i=1; i<=10; i++){
		printf("\t\tChild thread %d\n", i);
		sleep(1);
	}
	pthread_exit("Good Bye");
	// return (void *)1;
	//pthread_exit((void *)0);
}

6-1) 실행 결과 분석 (null이 나온 이유)

마지막 출력 결과가 Thread joined, it returned (null)인 이유는 코드 마지막 줄 때문입니다.

// 실제 실행된 코드
pthread_exit((void *)0);
  • 0은 주소로 치면 NULL입니다.
  • 메인 함수의 printf("%s", ...)NULL 포인터를 받아서 (null)이라고 출력한 것입니다.
  • 만약 주석 처리된 pthread_exit("Good Bye");를 풀었다면, "Thread joined, it returned Good Bye"가 출력되었을 것입니다.

6-2) pthread_joinvoid (이중 포인터) 이해하기

  • 자식 쓰레드가 남긴 "데이터의 주소(void *)"를 받아오고 싶을 때 사용한다.
  • 내가 가진 포인터 변수(return_value)의 주소(&return_value)를 줘야, 함수가 그 안에 자식의 주소를 채워줄 수 있다.
void *ptr;          // 1. 자식의 보물을 가리킬 빈 지도
pthread_join(tid, &ptr); // 2. "이 지도에 보물 위치 좀 적어줘" (지도의 주소를 넘김)
// 3. 이제 ptr은 자식이 리턴한 값을 가리킴

6-3) pthread_exit() vs return

쓰레드 함수(thread_function) 끝에서 두 방식은 동일하게 동작합니다.

  1. return (void*)val;: C언어 문법. 함수가 끝나면서 값을 반환.
  2. pthread_exit((void*)val);: 쓰레드 전용 함수. 명시적으로 종료 알림.

차이점: pthread_exit는 함수 깊은 곳(중첩된 함수)에서 호출해도 즉시 쓰레드를 종료시킬 수 있습니다.


6-4) 좀비 쓰레드 (Zombie Thread) 주의

joinable 쓰레드를 join 하지 않으면 좀비가 됩니다.

  • 살아있을 때: 스택(Stack) + 레지스터 등 자원 사용.
  • 죽었지만 join 안 함 (좀비): 종료 코드(Exit Code)를 담은 최소한의 메모리가 커널에 계속 남음.

해결방법

  1. 반드시 pthread_join()을 호출한다.
  2. 또는 pthread_detach()로 "결과 필요 없으니 알아서 사라져"라고 설정한다.

6-5) 코드 개선 팁 (메모리 누수 방지)

thread_function 내부의 mallocfree 되지 않았습니다. 아래처럼 자원을 정리하거나, join한 곳에서 free 할 수 있도록 리턴해줘야 합니다.

C

void *thread_function(void *arg){
    // ... (생략) ...
    // tpid, thread_id를 malloc 했지만 리턴하지 않고 함수가 끝남 -> Memory Leak
    
    // 해결책 1: 다 썼으면 해제하기
    free(tpid);
    free(thread_id);
    
    pthread_exit("Good Bye");
}
profile
임베디드 개발자가 되기 위해 공부중입니다!

2개의 댓글

comment-user-thumbnail
2026년 2월 24일

Thread 함수에 데이터를 넘기고 싶을땐 어떻게 해야하나요? 그리고 C++ boost thread와 posix thread의 차이가 있나요?

1개의 답글