Embedded/RaspberryPI

라즈베리파이(Raspberry Pi)에서 스레드(pthread) 테스트

변화의 물결1 2025. 1. 12. 16:31

 

안녕하세요.

 

 읽고 있는 책(하단 참고 도서 참고)에 내용 중 pthread 내용  조금 수정이 필요한 코드 부분이 있어 수정과 확인을 위해서 공유합니다.

 


 

1. POSIX Threads (pthread)란?

  • POSIX Threads(pthreads)는 POSIX (Portable Operating System Interface) 표준을 따르는 멀티스레딩 API입니다.
  • 멀티스레딩(multi-threading)을 지원하여 하나의 프로세스 내에서 여러 스레드가 동시에 실행될 수 있도록 설계되었습니다.
  • Unix 계열 시스템(Linux, macOS 등)에서 널리 사용되며, C 및 C++에서 멀티스레드를 구현하는 표준 방법입니다.

2. 주요 특징

  • 경량 프로세스: 스레드는 동일한 프로세스 내에서 실행되므로 메모리 공간과 리소스를 공유합니다.
  • 독립 실행: 각 스레드는 독립적인 실행 흐름을 가지며, 별도의 스택을 사용합니다.
  • 효율성: 프로세스 간 통신보다 스레드 간 통신이 더 빠릅니다.
  • 표준화: POSIX 표준에 기반하여 작성되어 플랫폼 간 이식성이 높습니다.

3. 스레드와 프로세스의 차이

항목 프로세스 스레드
독립성 서로 독립적 동일 프로세스 내에서 실행
메모리 사용 각 프로세스는 독립된 메모리 공간을 가짐 메모리와 자원을 공유
생성 속도 상대적으로 느림 빠름
통신 방식 IPC (파이프, 소켓 등) 사용 공유 메모리를 통해 직접 통신
컨텍스트 스위칭 비용 비교적 높음 낮음

4. pthread 기본 함수

  1. pthread_create
    • 새로운 스레드를 생성합니다.
    • 프로토타입:
      int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                         void *(*start_routine)(void *), void *arg);
    • 매개변수:
      • pthread_t *thread: 생성된 스레드의 ID를 저장할 변수
      • const pthread_attr_t *attr: 스레드의 속성을 지정 (NULL이면 기본 속성 사용)
      • void *(*start_routine)(void *): 스레드에서 실행할 함수
      • void *arg: 실행 함수에 전달할 인자
  2. pthread_exit
    • 스레드의 실행을 종료합니다.
    • 프로토타입:
      void pthread_exit(void *retval);
  3. pthread_join
    • 특정 스레드의 종료를 기다립니다.
    • 프로토타입:
      int pthread_join(pthread_t thread, void **retval);
  4. pthread_self
    • 현재 실행 중인 스레드의 ID를 반환합니다.
    • 프로토타입:
      pthread_t pthread_self(void);
  5. pthread_equal
    • 두 스레드 ID가 같은지 비교합니다.
    • 프로토타입:
      int pthread_equal(pthread_t t1, pthread_t t2);
  6. pthread_cancel
    • 특정 스레드의 실행을 취소합니다.
    • 프로토타입:
      int pthread_cancel(pthread_t thread);

5. 스레드 동기화

멀티스레드 환경에서는 공유 자원 접근 시 동기화가 필요합니다. POSIX 스레드는 다음과 같은 동기화 메커니즘을 제공합니다:

  1. 뮤텍스 (Mutex)
    • 상호 배제를 통해 공유 자원에 대한 동시 접근을 방지합니다.
    • 주요 함수:
      • pthread_mutex_init: 뮤텍스 초기화
      • pthread_mutex_lock: 뮤텍스를 잠금
      • pthread_mutex_unlock: 뮤텍스를 해제
      • pthread_mutex_destroy: 뮤텍스를 해제 및 제거
  2. 조건 변수 (Condition Variable)
    • 스레드가 특정 조건이 만족될 때까지 대기하거나 다른 스레드에 신호를 보낼 수 있게 합니다.
    • 주요 함수:
      • pthread_cond_wait: 조건 변수에서 대기
      • pthread_cond_signal: 조건 변수에 신호를 보냄
      • pthread_cond_broadcast: 모든 대기 중인 스레드에 신호를 보냄
  3. 읽기-쓰기 잠금 (Read-Write Lock)
    • 읽기와 쓰기를 구분하여 동기화를 제공합니다.
    • 주요 함수:
      • pthread_rwlock_init: 읽기-쓰기 잠금 초기화
      • pthread_rwlock_rdlock: 읽기 잠금
      • pthread_rwlock_wrlock: 쓰기 잠금
      • pthread_rwlock_unlock: 잠금 해제
      • pthread_rwlock_destroy: 잠금 제거

6. 스레드 속성

스레드 속성은 pthread_attr_t를 통해 설정할 수 있습니다. 주요 속성:

  1. 분리 상태 (Detached State)
    • 스레드가 종료된 후 리소스를 자동으로 반환할지 여부를 설정합니다.
    • pthread_attr_setdetachstate 함수로 설정 가능:
      • PTHREAD_CREATE_JOINABLE (기본값): pthread_join을 통해 종료 대기 가능
      • PTHREAD_CREATE_DETACHED: 자동으로 리소스 반환
  2. 스택 크기 (Stack Size)
    • 스레드 스택 크기를 설정합니다.
    • pthread_attr_setstacksize 함수로 설정 가능
  3. 우선순위 스케줄링
    • 스레드의 우선순위를 설정할 수 있습니다.
    • pthread_attr_setschedpolicypthread_attr_setschedparam 함수로 설정 가능

7. 스레드 ID (pthread_t)

  • pthread_t는 스레드 ID를 나타내는 opaque object(불투명 객체)입니다.
    • 플랫폼에 따라 정수형, 포인터, 또는 구조체로 구현될 수 있습니다.
    • 두 스레드 ID를 비교할 때는 pthread_equal()을 사용해야 합니다.

8. 예제 코드 1

간단한 멀티스레드 프로그램:

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

void* threadFunc(void* arg) {
    int id = *(int*)arg;
    printf("Thread %d is running\n", id);
    sleep(1);
    printf("Thread %d is exiting\n", id);
    return NULL;
}

int main() {
    pthread_t threads[3];
    int thread_ids[3] = {1, 2, 3};

    for (int i = 0; i < 3; i++) {
        pthread_create(&threads[i], NULL, threadFunc, &thread_ids[i]);
    }

    for (int i = 0; i < 3; i++) {
        pthread_join(threads[i], NULL);
    }

    printf("All threads have finished\n");
    return 0;
}


8. 예제 코드 2 ( 도서 내용 예제 수정 )

사용자가 특정 문자를 입력했을 때, 스레드가 생성하여 스레드 ID를 보여주고 스레드 취소 및 종료하는 프로그램

PS 출력 결과를 보면 부모 프로세서에 다른 스레드 LWP(Light Weight Process)가 생성된 것을 확인할 수 있습니다 :

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>
#define MAX_THREAD_NUM 100

void* newThread(void *arg){
        pthread_t tid;
        tid = pthread_self();
        //printf("thread[%lu] start\n", tid);
        pid_t lwp = syscall(SYS_gettid);
        printf("pthread_self() ID: %lu, LWP (syscall): %d\n", (unsigned long)tid, lwp);
    if ((unsigned long)tid == (unsigned long)lwp) {
        printf("pthread_t and LWP are identical!\n");
    } else {
        printf("pthread_t and LWP differ.\n");
    }
        pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); //
        while(1){
                sleep(1);
                printf("thread[%lu] work\n", tid);
        }//while
}//newThread

void main(void) {
int result;
pthread_t thread[MAX_THREAD_NUM];
int thread_num = 0;

printf("n: make new thread\n"
                "d: delete last created thread\n"
                "h: help\n"
                "e: exit\n");

while(1) {
        char inputChar;
        scanf("%c", &inputChar);
        switch(inputChar) {
                case 'n': {
                        printf("create thread");
                        result = pthread_create(&thread[thread_num],NULL,newThread,NULL);
                        if(result) {
                                printf(" ERROR [%d]\n", result);
                        } //if
                        else {
                                printf(" SUCCESS [%lu]\n", thread[thread_num]);
                                thread_num++;
                        }//else
                }//n
                break;
                case 'd': {
                        int i;
                        if(thread_num){
                                printf("delte thread");
                                result = pthread_cancel(thread[thread_num-1]);
                                if(result) {
                                        printf(" ERROR [%d]\n", result);
                                } //if
                                else {
                                        printf(" SUCCESS [%lu]\n", thread[thread_num-1]);
                                        thread_num--;
                                }//else
                        }//if
                        else {
                                printf("There is no thread to delete.\n");
                        }//else
                }//d
                case 'h' : {
                        printf("n: make new thread\n"
                                        "d: delete last created thread\n"
                                        "h: help\n"
                                        "e: exit\n");
                }// h
                break;
                case 'e' : {
                        printf("Exit Program\n");
                        return;
                }
                break;
                default:
                break;
        }//switch
}//while
}//main

 

 

2개의 터미널을 실행시켜 터미널 창 1은 프로그램 빌드와 프로그램 실행을 터미널 창 2는 프로세서와 스레드가 생성 확인을 합니다.

 

<터미널 창 1>

$ gcc ex_pthread.c -o ex_pthread -lpthread
$ ./ex_pthread

 

 

<터미널 창 2>

$ ps -eLf | grep ex_thread

 

 


9. 참고 사항

  1. 스레드는 가벼운 실행 단위이지만, 올바른 동기화 없이는 경쟁 상태(race condition)가 발생할 수 있습니다.
  2. 리소스 공유 시 동기화 메커니즘(뮤텍스, 조건 변수 등)을 적절히 사용해야 합니다.
  3. 스레드 생성 및 관리 시, 적절한 리소스 해제가 중요합니다.

 

 

 

<참고 자료>

1. [도서] 임베디드 소프트웨어 베이직, 12.1 쓰레드 실습 - pthread

 

 

반응형