이 글은 윤성우의 TCP/IP 소켓 프로그래밍 책을 참고하였습니다.

순서

I. 스레드의 생성
pthread_create 함수

II. 프로세스와의 동기화
pthread_join함수


I. 스레드의 생성

함수를 먼저 보자.

#include <pthread.h>

int pthread_create( pthread_t *restrict thread, const pthread_attr_t *restrict attr,
                    void* (*start_routine)(void*), void *restrict arg);
                    
    --> 성공 시 0, 실패 시 0 이외의 값 반환
    
    ㄴ. thread        : 생성할 스레드의 ID 저장을 위한 변수의 주소 값 전달, 
                        프로세스와 마찬가지로 스레드의 구분을 위한 ID가 부여된다.
                        
    ㄴ. attr          : 스레드에 부여할 특성 정보의 전달을 위한 매개변수, NULL 전달 시 기본특성.
    
    ㄴ. start_routine : 스레드의 main 함수 역할을 하는,
                        변도 실행흐름이 시작되는 함수의 주소값(함수 포인터) 전달.
                        
    ㄴ. arg           : 세 번째 임자를 통해 등록된 함수가 호출될 때 전달할 인자의 주소전달

스레드를 사용하는 데에는 restrice와 함수 포인터 문법을 잘 알아야 하지만,
단순히 사용방법만 서술할 것이다.

인자1. pthread_t *restrict thread

프로세스를 생성할 때에도 fork()를 통해 PID를 얻은 바가 있다.

이처럼 스레드도 pthread_t의 자료형을 가지는 스레드의 ID가 필요한데,
이 변수를 미리 선언하여 인자로 넣어두면 된다.

인자2. const pthread_attr_t *restrict attr

NULL로 기본 특성 보내자.

인자3. start_routine

스레드도 하나의 별도 실행 흐름을 가진다. 우리는 이걸 함수로 만들 것이고
이 함수의 주소 값, 즉 함수 포인터를 전달하면 된다.

그러면 그 함수를 실행하게 되는 것이다.

인자4. arg

만약 함수 포인터로 전달한 그 메인 스레드 함수에 인자가 필요하다면,
미리 정의해서 그 주소를 보내야 한다.

이를 이용해서 구현하면

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

void* thread_main(void *arg);

int main()
{
    pthread_t thread_ID;
    int thread_arg = 5;
    
    if(pthread_create(&thread_ID, NULL, thread_main, (void*)&thread_arg) != 0)
    {
        puts("pthread_create() ERR!\n");
        return -1;
    }
    
    sleep(10); puts("end of main\n");
    return 0;
}

void* thread_main(void *arg)
{
    int cnt = *((int*) arg); // void포인터 형을 int 포인터 형으로 캐스팅
    
    for(int i=0;i<cnt;i++)
    {
        sleep(1);
        puts("running thread %d\n",i);
    }
    return NULL;
}

실행 결과
running thread 0
running thread 1
running thread 2
running thread 3
running thread 4
end of main

특이한 점이 두 가지 있다.

1. main함수를 끝낼 때 sleep을 한 점
2. 인자를 void*형으로 보내는 것.

1.의 이유는 현재 우리가 pthread_create로만 구현을 했기 때문에
프로세스가 죽으면서 스레드도 같이 죽어 스레드 함수 내부를 충분히 실행을 못 시킬 수 있기 때문이다.

2.의 이유이식성 때문에 그럴 것이라고 생각한다.
함수 포인터를 사용할 때 void로 모든 형을 받아오고, 함수 내에서 필요한 자료형으로 캐스팅을 쓰는 것이
이식성도 좋고 범용적이지 않을까...?

 


II. 프로세스와의 동기화

앞서 우리는 프로세스가 끝날 때 스레드도 같이 종료되었다.
그렇다면, 스레드가 돌고 있는 동안에 프로세스를 잠시 대기시킬 순 없을까?

이 문제점을 pthread_join함수가 해결해준다.

#include <pthread.h>

int pthread_join(pthread_t thread, void **status);
    --> 성공 시 0, 실패 시 0 이외의 값 반환
    
    ㄴ. thread : thread ID로 이 매개변수의 ID를 가진 스레드가 종료하기 전까지 함수는 반환하지 않음
    
    ㄴ. status : 스레드의 main 함수가 반환하는 값이 저장될 포인터 변수의 주소 값을 전달 


인자1. pthread_t thread

pthread_create에 우리는 pthread_t의 자료형으로 선언된 변수의 주소 값을 인자로 넣었고,
그 결과로 그 변수 안에 thread ID가 저장되어 있다.

따라서 원하는 스레드의 ID변수를 넣어주면 된다.(주소 X)

이 ID를 가진 스레드가 끝나기 전까지 프로세스(또는 스레드)를 대기상태에 둔다.


인자2. void **status

함수의 반환이 끝나면 그 반환된 값을 저장할 포인터 변수의 주소 값을 전달한다.

중요한 점은 포인터 변수의 주소 값이다.
= 변수의 주소의 주소 값

그 이유는 우리는 함수 포인터로 함수를 정의하였기 때문에
반환 값이 값의 포인터 변수가 되기 때문이다.

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

void* thread_main(void *arg);

int main()
{
    pthread_t thread_ID;
    int thread_arg = 5;
    void* thr_ret;
    
    if(pthread_create(&thread_ID, NULL, thread_main, (void*)&thread_arg) != 0)
    {
        puts("pthread_create() ERR!\n");
        return -1;
    }
    
    if (pthread_join(thread_ID, &thr_ret) != 0)
    {
        puts("pthread_join() ERR!\n");
        return -1;
    }
    
    puts("end of main, retmsg = %s",(char*)thr_ret);
    return 0;
}

void* thread_main(void *arg)
{
    int cnt = *((int*) arg); // void포인터 형을 int 포인터 형으로 캐스팅
    
    char* retmsg = "Hello";
    
    for(int i=0;i<cnt;i++)
    {
        sleep(1);
        puts("running thread %d\n",i);
    }
    return (void*)retmsg;
}

실행 결과
running thread 0
running thread 1
running thread 2
running thread 3
running thread 4
end of main, retmsg = Hello

+ Recent posts