쓰레드에 대한 더 많은 정보를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

스레드(Thread)

 

스레드는 한국어로 바꿔말하면 실이라고 합니다. 우선 개념부터 잡고 갑시다.

스레드는 어떤 프로그램에서 프로세스가 실행되는 흐름의 단위를 말합니다.

프로세스는 적어도 하나의 스레드를 갖고 있습니다. 우리가 흔히 알고 있는 main함수가 바로 그 스레드지요.

 

멀티 프로세스와 멀티 스레드는 흐름이 동시에 진행된다는 것에서 공통점을 갖습니다. 하지만 프로세스와는 다르게 메모리를 공유한다는 점이 다른데요.

아래의 그림과 같이 스레드간 공유되는 영역은 code, data, file입니다. 스택은 스레드마다 독립적으로 갖고 있다는 것을 알 수 있지요.

 

 

 

리눅스의 스레드

리눅스에서 스레드를 생성하려고 한다면 pthread.h의 헤더파일을 포함해야합니다.

 

pthread_create

스레드를 생성합니다. 함수 원형은 이렇습니다.

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg)

 

이 함수는 현재 실행되고 있는 프로세스에서 새로운 스레드를 생성하는 함수입니다.

 

1. thread : 성공적으로 함수가 호출되면 이곳에 thread ID가 저장됩니다. 이 인자로 넘어온 값을 통해서 pthread_join과 같은 함수를 사용할 수 있습니다.

2. attr : 스레드의 특성을 정의합니다. 기본적으로 NULL을 지정합니다. 만약 스레드의 속성을 지정하려고 한다면 pthread_attr_init등의 함수로 초기화해야합니다.

3. start_routine : 어떤 로직을 할지 함수 포인터를 매개변수로 받습니다. 

4. arg : start_routine에 전달될 인자를 말합니다. start_routine에서 이 인자를 변환하여 사용합니다.

성공시에 thread를 초기화함과 동시에 0을 반환하게 됩니다. 실패시에는 thread인자가 설정되지 않고 에러 값을 반환하게 되지요.

 

아래의 코드가 pthread_create를 사용한 예를 보여줍니다.

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

void* thread_routine(void *arg){  
        pthread_t tid;    
        tid=pthread_self();  

        int i=0;    
        printf("\ttid:%lx\n",tid);   
  
        while(i<10){    
                printf("\tnew thread:%d\n",i);    
                i++;        
                sleep(1);  
        } 
} 

int main(){    
        pthread_t thread;   
        pthread_create(&thread,NULL,thread_routine, NULL);   
        int i=0;  
        printf("tid:%lx\n",pthread_self());     
    
        while(i<5){     
                printf("main:%d\n",i);     
                i++;        
                sleep(1);   
        } 
}

pthread_create를 통해서 thread_routine을 실행하고 있지요. 코드를 더 간략하게 하기 위해서 오류처리는 하지 않았습니다.

 

gcc pthread.c -lpthread -o pthread의 명령을 주고나서 pthread라는 프로그램을 실행시켜보도록 하겠습니다.

 

tid:ac402740

main:0

        tid:abc11700

        new thread:0

main:1

        new thread:1

main:2

        new thread:2

main:3

        new thread:3

main:4

        new thread:4

 

메인스레드와 thread라는 스레드는 각자의 thread id를 출력하고 1초에 한번씩 i의 값을 증가시키면서 출력해주고 있습니다. thread id는 서로 다르다는 것을 알 수 있습니다.

 

자. thread_routine은 0부터 9까지 1초에 한번씩 출력해줘야하는 우리의 의도와는 다르게 main이 끝나면서 동시에 끝나게 됩니다.

우리는 thread_routine이 끝날때까지 프로그램을 멈추고 싶지 않습니다. 이 의도를 달성하기 위해서 우리는 어떻게 해결해야할까요?

 

pthread_join

우리의 목적을 달성하기 위한 함수입니다.

int pthread_join(pthread_t thread, void **retval)

pthread_join은 스레드를 생성했던 thread를 끝날때까지 기다려줍니다. 만약 thread가 이미 종료되었다면 즉시 리턴합니다.

 

1. thread : 우리가 join하려고 하는 thread를 명시해줍니다. pthread_create에서 첫번째 인자가 있었죠? 그 스레드가 join하길 원한다면 이 인자로 넘겨주면 됩니다.

2. retval : pthread_create에서 start_routine이 반환하는 반환값을 여기에 저장합니다. 

 

만약 성공적으로 호출이 되었다면 0을 반환합니다. 실패시 에러 넘버를 반환하게 되지요. 실패시에는 좀비 스레드가 되고 이 좀비 스레드는 자원을 소모하게 되어 더이상 스레드를 생성할 수 없게 된다고 하네요.

 

pthread_join을 통해서 thread_routine을 전부 실행시키도록 main 스레드에서 기다려 주도록 해볼게요.

 

int main{ 
        // 생략 //  
        while(i<5){      
                printf("main:%d\n",i);      
                i++;         
                sleep(1);   
        }     
    
        pthread_join(thread,NULL);
}

main함수 맨 아래에 pthread_join만 추가해주면 됩니다. thread_routine은 반환하는 값이 없으므로 두번째 인자는 NULL을 전달해주는 것이구요.

 

이제 결과를 한번 보도록하지요.

tid:47ee9740

main:0

        tid:476f8700

        new thread:0

main:1

        new thread:1

main:2

        new thread:2

main:3

        new thread:3

main:4

        new thread:4

        new thread:5

        new thread:6

        new thread:7

        new thread:8

        new thread:9

 

 

이제 thread_routine이 실행되고 끝나는 것을 볼 수가 있네요.

 

pthread_detach

 

때에 따라서는 스레드가 독립적으로 동작하길 원할 수도 있습니다. 단지 pthread_create 후에 pthread_join으로 기다리지 않구요. 나는 기다려주지 않으니 끝나면 알아서 끝내도록 하라는 방식입니다.

 

독립적인 동작을 하는 대신에 스레드가 끝이나면 반드시 자원을 반환시켜야합니다. pthread_create만으로 스레드를 생성하면 루틴이 끝나서도 자원이 반환되지 않습니다. 그러한 문제점을 해결해주는 함수가 바로 pthread_detach입니다. 

int pthread_detach(pthread_t thread)

thread는 우리가 detach 시킬 스레드입니다. 

성공시 0을 반환하며 실패시 오류 넘버를 반환하지요.

pthread_detach와 pthread_join을 동시에 사용할 수는 없습니다.

 

두번째 detach 방식은 바로 생성과 동시에 detach 시키는 방법입니다. pthread_create에서 두번째 인자 기억나시나요? attr에 detach정보를 주어 생성과 동시에 분리시키는 것이지요.

 int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

 

사용법은 이렇습니다.

pthread_attr_t attr;

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

pthread_create(.., &attr, ...)

pthread_attr_setdetachstate에는 PTHREAD_CREATE_DETACHED 뿐만 아니라 PTHREAD_CREATE_JOINABLE이라는 pthread_create 동시에 join 시킬 수도 있습니다.

두번째 방법이 훨씬 안전한 방법이라고 얘기해 드리고 싶군요. attr을 통해서 detach하는 방법 말이죠.

전자의 방법으로 했다가 정말 재수 없다면 pthread_detach 전에 스레드가 끝날 수 있는 상황이 발생하기 때문이지요.

리눅스의 스레드 기본 함수들과 사용법을 살펴보았습니다.

 

스레드 동기화

스레드를 사용할때는 주의해야함 점이 있습니다. 서로 동시에 실행하기 때문에 발생하는 공유자원의 잘못된 접근때문입니다. 그 때문에 공유자원을 하나의 스레드만 사용하게 하는 방법이 필요한데 이를 동기화라고 합니다. 

스레드를 통한 동기화는 아래의 포스팅을 참고하시기 바랍니다.

 

Mutex(pthread_mutex_init, pthread_mutex_lock, pthread_mutex_unlock, pthread_mutex_destroy)를 사용한 스레드 동기화 ->

 

https://reakwon.tistory.com/98

 

조건 변수의 사용은 아래의 포스팅을 참고하시기 바랍니다.

https://reakwon.tistory.com/99

 

반응형
블로그 이미지

REAKWON

와나진짜

,