'pthread'에 해당되는 글 3건

  1. 2008.12.23 Thread and Signal
  2. 2008.12.23 pthread_mutex_lock
  3. 2008.12.23 Pthread API Reference

JOINC.co.kr

쓰레드와 시그널

Date: 2003/10/27

Topic: 시스템 프로그램

윤상배: dreamyun@yahoo.co.kr

 

 

그렇잖아도 애매 모호한 쓰레드에 헷갈리는 시그널을 사용하고자 하면 여러 가지 애로 사항이 꽃피게 된다. 각 쓰레드별로 시그널이 전달되거나 전달되지 않도록 설정할 수 있어야 하기 때문인데, 개념적으로는 간단하지만 막상 적용하려면 그 과정이 머리에 그려지지 않기 때문이다.

 

1절. 쓰레드에서의 시그널 사용

 

쓰레드에서의 시그널 사용은 시그널에 대한 기본적인 이해만 가지고 있다면 약간의 응용으로 충분히 해결할 수 있는 문제이긴 하지만 범 유닉스적으로 응용하고자 한다면(특히 리눅스가 포함된다면) 운영체제 간 신경써줘야 할 문제가 있다. 이번 장에서는 쓰레드에서의 시그널을 이용하는 방법과 운영체제가 다름으로 인해 발생할 수 있는 문제들에 대해서 알아보도록 하겠다.

 

1.1절. 시그널을 특정 쓰레드로 보내기

 

쓰레드에서 시그널은 서로 공유된다는 걸 알고 있을 것이다. 문제는 공유된다는 점인데 만약 프로세스에 시그널을 보낼 경우 해당 프로세스에서 생성된 모든 쓰레드에 시그널이 전달이 되게 된다. 이것은 우리가 원하는게 아니다.

 

우리가 원하는 것은 특정 쓰레드에서만 시그널을 받도록 하는 것이다. 이러한 작업을 위해서 우리는 시그널 마스크를 사용한다. 시그널 마스크는 말 그대로 특정 시그널에 대해서 마스크를 씌우는 것으로 해당 쓰레드에서 특정 시그널에 대해서 마스크를 씌우면 마스킹된 시그널은 해당 쓰레드로 전달되지 않는다. 이 시그널을 받기를 원하는 쓰레드에서는 이 시그널에 대한 마스크를 제거시킨다. 그러면 블록되어 있는 시그널은 마스크가 제거된 쓰레드로 전달 될 것이다. 일종의 필터기다.

 

그림 1. 시그널 마스크의 작동원리

 

위의 그림은 시그널 마스크의 작동 원리를 보여준다. 메인 쓰레드에서는 SIGINT와 SIGUSR2에 대해서 시그널 마스크를 설치한다. 그리고 쓰레드 1에서는 SIGINT에 대한 마스크를 제거하고, 쓰레드 2에서는 SIGUSR2에 대한 마스크를 제거한다. 이렇게 되면 SIGINT가 메인 쓰레드에 도착했을 때 마스크 때문에 메인 쓰레드에는 도착하지 못하고 쓰레드 1로 전달될 것이다. SIGUSR2가 도착했을 경우 메인 쓰레드와 쓰레드 1에서는 마스크 때문에 전달되지 못하고 쓰레드 2로 시그널이 전달된다. 1.1.1절에서는 위의 작동 원리대로 구현된 예제 코드를 다루고 있다.

 

이러한 쓰레드별 시그널 마스킹을 위해서 pthread는 pthread_sigmask(3)라는 함수를 제공한다.

#include <pthread.h>
#include <signal.h>

 

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);

 

이 함수는 현재 쓰레드에 시그널 newmask와 how를 이용해서 시그널 마스크를 만든다. how는 SIG_BLOCK, SIG_UNBLOCK, SIG_SETMASK 중 하나를 선택할 수 있다. SIG_BLOCK은 현재 설정된 시그널 마스크에 newmask를 추가하며 SIG_UNBLOCK은 현재 설정된 시그널 마스크에서 newmask를 제거하고 SIG_SETMASK는 newmask로 현재 시그널 마스크를 설정한다.

 

1.1.1절. 간단 예제

 

그럼 pthread_mask(3)를 이용한 간단한 예제를 만들어 보도록 하겠다. 코드는 여러분이 시그널과 쓰레드에 관한 최소한의 지식을 가지고 있다는 가정하에 작성될 것이며, 설명은 주석으로 대신하도록 하겠다.

 

예제 : th_signal.c

위 프로그램을 실행시킨 뒤 kill 명령으로 SIGINT와 SIGUSR2 시그널을 PID로 보내보면 해당 쓰레드로 시그널이 전달되고 시그널 핸들러가 실행되는 걸 확인할 수 있을 것이다.

 

1.2절. 쓰레드 간 시그널 전송

 

외부의 다른 프로세스에서 시그널을 발생시키는 것 외에도 같은 프로세스에서 작동하는 쓰레드 간에 시그널을 전송해야 하는 경우도 생길 것이다.

 

이러한 쓰레드 간 시그널 전송은 여러 가지 목적으로 사용할 수 있다. 일정 시간마다 특정 쓰레드에 시그널을 전송하므로써 쓰레드를 깨워서 코드를 실행시키게 한다거나 네트워크 애플리케이션에서 write, read에 타임아웃을 검사하는 용도로도 사용 가능하다.

 

네트워크 애플리케이션에서 스레드 간 시그널 전달을 통해 타임아웃을 검사한다는 생각은 좀 생소할 수도 있을 것 같다. 보통은 select나 alarm을 사용할 건데, 멀티 쓰레드 프로그램의 경우 alarm(2)의 사용은 사실상 어렵다고 볼 수 있다. 여러 개의 쓰레드에서 alarm(2)를 사용할 경우 단지 하나의 alarm(마지막 alarm 값)만이 등록되어서 사용할 수 있기 때문이다. 그렇다면 select를 사용해야 할 건데, select 대신에 전용의 시그널을 발생하는 쓰레드를 이용해서 사용할 수 있다.

 

read(2)를 예로 들어서 설명해 보자 read(2)를 하기 전에 특정 (전역) 값을 0으로 세팅하고 read를 수행한 후 1로 값을 변경하도록 한다. 그리고 타임아웃 체크를 위한 쓰레드에서는 타임아웃 시간 간격으로(sleep(2)를 이용하면 된다) 이 값을 검사한다. 만약 값이 0으로 세팅되어 있는 걸 확인 했는데, 다음 시간이 돌아온 뒤에도 이 값이 0이라면 read 영역에서 타임아웃이 발생했다고 판단 할 수 있을 것이다. 그러면 타임아웃이 발생한 쓰레드에 시그널을 전송하도록 한다. 쓰레드에 시그널을 전송하면 인터럽트가 발생하고 read에서 빠져나오게 된다.

if (read(..) < 0)
{
        // 만약 인터럽트로 인하여 빠져나온 거라면..
        if (errno == EINTR)
        {
                ...
        }
}

시그널 발생시 인터럽트가 전달되게 하려면 약간의 부가적인 작업이 필요한데, 이것은 소켓 타임아웃을 참고하기 바란다.

 

1.2.1절. 다른 쓰레드로 시그널 전송

 

이러한 쓰레드 간 시그널 전송을 위해서 pthread_kill(3)가 제공된다.

#include <pthread.h>
#include <signal.h>


int pthread_kill(pthread_t thread, int signo);

 

첫 번째 인자 thread는 시그널을 전달받을 쓰레드의 식별자이고 signo은 전달하고자 하는 시그널 번호이다. 보내는 쪽은 pthread_kill(3)을 이용해서 비교적 간단하게 구현이 가능하다.

 

1.2.2절. 시그널받기

 

시그널을 받는 쓰레드의 경우 동기와 비동기 두 가지 방식을 통해서 받을 수 있다. 동기 방식으로 받을 경우는 sigwait(3) 함수를 이용해서 시그널이 전달될 때까지 블록되면서 기다린다.

#include <pthread.h>
#include <signal.h>

 

int sigwait(const sigset_t *set, int *sig);

 

이 함수는 시그널 set에 설정된 시그널 중 하나가 전달될 때까지 호출된 영역에서 대기한다. 시그널을 받았다면 리턴되고 전달 받은 시그널 번호는 sig를 통해서 넘어온다. 시그널을 기다린다는 특징을 이용해서 쓰레드 간 동기화를 위한 목적으로도 유용하게 사용할 수 있을 것이다.

 

두 번째는 비동기적인 방식으로 코드 실행 중에 시그널이 전달되면 인터럽트가 걸리고 시그널 핸들러가 수행되는 방식이다. 일반적인 시그널 사용 방식과 동일하다.

 

1.2.3절. 예제

 

sigwait(3)를 통해서 동기적으로 기다리는 것은 구현이 간단하므로 따로 다루지 않고 시그널 핸들러를 등록해서 비동기적으로 시그널을 기다리는 코드를 구현해 보도록 하겠다. 1.1.1절의 코드를 약간 수정했다.

 

예제 : thtoth_sig.c

위의 코드의 경우 시그널을 받을 쓰레드를 명시해줄 수 있으므로 시그널 마스크 등을 설치할 필요가 없다. SIGINT가 원하는 쓰레드로 정확하게 전달되는 걸 확인할 수 있을 것이다.

 

1.3절. 운영체제별 차이점

 

쓰레드의 작동 방식은 운영체제별로 많은 차이를 보여줄 수 있으며, 차이점에 유의해서 프로그램을 작성해야 한다. 여기에서는 솔라리스와 리눅스를 비교해서 설명하도록 하겠다.

 

지금까지의 쓰레드와 시그널에 대해서 다루었던 내용은 솔라리스와 같이 하나의 프로세스에서 다중의 쓰레드를 관리하는 경우를 기준으로 했다. 그러나 리눅스의 경우 clone(2)를 통한 다중 프로세스 형태로 쓰레드가 생성된다. 때문에 ps를 이용해서 확인할 경우 다중 쓰레드 프로세스임에도 불구하고 각각의 PID를 가지는 프로세스로 쓰레드가 생성되는걸 확인 할 수 있다.

 

이런 특징 때문에 리눅스 시스템에서 외부 프로세스에서 시그널을 특정 쓰레드로 보낼 경우에는 메인 쓰레드가 아닌 해당 쓰레드의 PID를 명시해 주어야 한다.

 

This article comes from Joinc

http://www.joinc.co.kr


Posted by 두장

1장. pthread_mutex_lock(3)

차례
1.1절. 사용법
1.2절. 설명
1.3절. 반환값
1.4절. 에러
1.5절. 예제
1.6절. 참고문헌

뮤텍스 잠금을 얻거나 해제한다.


1.1절. 사용법

#include <pthrad.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
		


1.2절. 설명

mutex는 MUTual EXclusion(상호 배제)devide의 줄임말로 쓰레드간 공유하는 데이터 영역을 보호하기 위해서 사용한다. 데이터 영역의 보호는 critical section(임계 영역)을 만들고 임계 영역내에 단하나의 쓰레드만이 진입가능 하도록 하는 방식을 사용한다.

보통 이 임계영역에는 보호하고자 하는 데이터에 대한 접근/수정 루틴이 들어간다. 데이터에 대한 접근/수정 루틴에 오직 하나의 쓰레드만 접근 가능하게 되므로 결국 데이터를 보호할 수 있게 된다.

뮤텍스는 단지 2개의 가능한 행동만이 정의되어 있다. unlock와 lock이 그건데, lock는 임계영역은 진입하기 위한 요청, unlock는 임계영역을 빠져나오면서 다른 쓰레드에게 임계영역을 되돌려주기 위해서 사용한다. 만약 쓰레드가 임계영역이 진입하기 위해서 lock를 시도 했는데, 다른 쓰레드가 이미 임계영역에 진입했다면 해당 쓰레드가 unlock를 해서 임계영역을 빠져나오기 전까지 기다리게 된다.

mutex는 fast recursive의 2가지 종류가 지원된다. 이것은 lock을 얻은 쓰레드가 다시 lock를 얻을 수 있도록 할 것인지를 결정하기 위해서 사용한다. 기본적으로 mutex의 종류는 fast 상태로 시작된다. mutex 종류에 대한 자세한 내용은 pthread_mutexattr_init(3)을 참고하기 바란다.

pthread_mutex_t는 뮤텍스의 특징을 결정하기 위해서 사용한다.PTHREAD_MUTEX_INITIALIZER(fast mutex)와PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP(recursive mutexe), PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP(mutx 에러 체크용)의 3가지 상수가 준비되어 있다. 이중 하나를 선택하면 된다.

pthread_mutex_lock()는 (임계영역에 진입하기 위함)뮤텍스 잠금을 요청한다. 만약 뮤텍스의 최근 상태가 unlocked라면 쓰레드는 잠금을 얻고 임계영역에 진입하게 되고 리턴한다. 다른 쓰레드가 뮤텍스 잠금을 얻은 상태라면 잠금을 얻을 수 있을 때까지 기다리게 된다.

pthread_mutex_unlock()는 뮤텍스잠금을 되돌려준다. 만약 fast 뮤텍스라면 pthread_mutex_unlock()는 언제나 unlocked 상태를 되돌려준다. recursive 뮤텍스라면 잠겨있는 뮤텍스의 수를 감소시키고 이 수가 0이 된다면 뮤텍스잠금을 되돌려주게 된다.

pthread_mutex_destory()는 뮤텍스 객체를 삭제하고 자원을 되돌려준다. 더이상 사용하지 않는 뮤텍스는 반드시 이 함수를 이용해서 삭제하도록 하자. 리눅스에서 쓰레드는 뮤텍스 객체와 별개로 되어 있다. 그러므로 쓰레드가 종료되었다고 하더라도 뮤텍스 객체는 여전히 남아 있게 된다. 이 함수를 호출해야지만 뮤텍스 객체가 삭제 된다.


1.3절. 반환값

pthread_mutex_init()는 언제나 0을 리턴한다. 다른 뮤텍스 함수들은 성공했다면 0, 실패했다면 0이 아닌 수를 리턴하고 errno를 설정한다.


1.4절. 에러

pthread_mutex_lock()함수는 아래의 에러코드를 반환한다.

EINVAL

뮤텍스가 잘못 초기화 되었다.

EDEADLK

이미 잠금을 얻은 쓰레드가 다시 잠금을 요청할 때 (error checking 뮤텍스일 경우 사용할 수 있다)

pthread_mutex_trylock()함수는 아래의 에러코드를 반환한다.

EBUSY

뮤텍스가 잠겨 있어서 잠금을 얻을 수 없다.

EINVAL

뮤텍스가 잘못 초기화 되었다.


1.5절. 예제

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

using namespace std;

void *ping(void *);
void *pong(void *);

pthread_mutex_t sync_mutex;
pthread_cond_t  sync_cond;

pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  gcond  = PTHREAD_COND_INITIALIZER;

int main()
{
    vector<void *(*)(void *)> thread_list;
    vector<pthread_t> tident(10); 
    int thresult;
    int status;
    int i;

    pthread_mutex_init(&sync_mutex, NULL);
    pthread_cond_init(&sync_cond, NULL);

    thread_list.push_back(pong);
    thread_list.push_back(ping);

    for(i = 0; i < thread_list.size(); i++ )
    {
        pthread_mutex_lock(&ync_mutex);
        if (pthread_create(&tident[i], NULL, thread_list[i], (void *)NULL) <0)
        {
            perror("error:");
            exit(0);
        }
        pthread_cond_wait(&sync_cond, &sync_mutex);
        pthread_mutex_unlock(&sync_mutex);
    }
    for (i = 0; i < tident.size(); i++)
    {
        pthread_join(tident[i], (void **)&status);
    }
}

void *ping(void *data)
{
    int i=0;
    pthread_mutex_lock(&sync_mutex);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        printf("%d : ping\n", i);
        pthread_cond_signal(&gcond);
        pthread_cond_wait(&gcond, &gmutex);
        pthread_mutex_unlock(&gmutex);
        usleep(random()%100);
        i++;
    }
}

void *pong(void *data)
{
    int i = 0;
    pthread_mutex_lock(&sync_mutex);
    sleep(1);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        pthread_cond_wait(&gcond, &gmutex);
        printf("%d : pong\n", i);
        pthread_cond_signal(&gcond);
        pthread_mutex_unlock(&gmutex);
        i++;
    }
}
		


Posted by 두장
article_Pthread_API_Reference위키 홈으로


Pthread API Reference

윤 상배

고친 과정
고침 0.9 2004년 6월 30일 12시
pthread 취소관련 api 추가
고침 0.8 2003년 10월 9일 12시
pthread 시그널 관련 api 추가

1. 소개

이 문서는 pthread 레퍼런스 문서이다. pthread 에서 제공하는 모든 함수의 레퍼런스를 제공하고 있지는 않지만, 자주 쓰일만한 대부분의 함수들은 정리되어 있음으로 참고할만한 가치가 있을것이다.

이 문서에 빠진 내용들은 계속 추가해 나갈 예정이다.


2. 기본 쓰레드 함수

주로 쓰레드 생성과 종료에 관련된 가장 기본적인 함수들이다.


2.1. pthread_create

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
			
쓰레드 생성을 위해서 사용한다. 첫번째 아규먼트인 thread 는 쓰레드가 성공적으로 생성되었을때 생성된 쓰레드를 식별하기 위해서 사용되는 쓰레드 식별자이다. 두번째 아규먼트인 attr 은 쓰레드 특성을 지정하기 위해서 사용하며, 기본 쓰레드 특성을 이용하고자 할경우에 NULL 을 사용한다. 3번째 아규먼트인 start_routine는 분기시켜서 실행할 쓰레드 함수이며, 4번째 아규먼는인 arg는 쓰레드 함수의 인자이다.

성공적으로 생성될경우 0을 리턴한다.

예제 : pthread_create.cc

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

// 쓰레드 함수
void *t_function(void *data)
{
    int id;
    int i = 0;
    id = *((int *)data);

    while(1)
    {
        printf("%d : %d\n", id, i);
        i++;
        sleep(1);
    }
}

int main()
{
    pthread_t p_thread[2];
    int thr_id;
    int status;
    int a = 1;
    int b = 2;

    // 쓰레드 생성 아규먼트로 1 을 넘긴다.  
    thr_id = pthread_create(&p_thread[0], NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }

    // 쓰레드 생성 아규먼트로 2 를 넘긴다. 
    thr_id = pthread_create(&p_thread[1], NULL, t_function, (void *)&b);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }

    // 쓰레드 종료를 기다린다. 
    pthread_join(p_thread[0], (void **)&status);
    pthread_join(p_thread[1], (void **)&status);

    return 0;
}
			
실행된 쓰레드에 대해서는 pthread_join 등의 함수를 이용해서 쓰레드 종료때까지 기다려줘야 한다. ptherad_join 은 일종의 fork 의 wait 와 비슷하게 작동하며, 쓰레드자원을 해제 시켜준다.


2.2. pthread_join

#include <pthread.h>
int pthread_join(pthread_t th, void **thread_return);
			
첫번째 아규먼트 th는 기다릴(join)할 쓰레드 식별자이며, 두번째 아규먼트 thread_return은 쓰레드의 리턴(return) 값이다. thread_return 이 NULL 이 아닐경우 해다 포인터로 쓰레드 리턴 값을 받아올수 있다.

pthread_joinc.c

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

// 쓰레드 함수 
// 1초를 기다린후 아규먼트^2 을 리턴한다. 
void *t_function(void *data)
{
    int num = *((int *)data);
    printf("num %d\n", num);
    sleep(1);
    return (void *)(num*num);
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;

    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    // 쓰레드 식별자 p_thread 가 종료되길 기다렸다가 
    // 종료리턴값을 가져온다. 
    pthread_join(p_thread, (void *)&status);
    printf("thread join : %d\n", status);

    return 0;
}
			


2.3. pthread_detach

int pthread_detach(pthread_t th);
			
detach 는 "떼어내다" 라는 뜻을 가지며 main 쓰레드에서 pthread_create 를 이용해 생성된 쓰레드를 분리시킨다. 이 함수는 식별번호th인 쓰레드를 detach 시키는데, detach 되었을경우 해당(detach 된) 쓰레드가 종료될경우 pthread_joinc 을 호출하지 않더라도 즉시 모든 자원이 해제(free) 된다.

여기에서는 pthread_create 호출후 detach 하는 방법을 설명하고 있는데, pthread_create 호출시에 쓰레드가 detach 되도록 할수도 있다. 이에 대한 내용은 pthread_attr_setdetachstate 를 다루면서 설명하도록 하겠다.

예제 : pthread_detach.c

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

// 쓰레드 함수
// 1초를 기다린후 아규먼트^2 을 리턴한다.
void *t_function(void *data)
{
    char a[100000];
    int num = *((int *)data);
	printf("Thread Start\n");
    sleep(5);
	printf("Thread end\n");
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;

	printf("Before Thread\n"); 
    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    // 식별번호 p_thread 를 가지는 쓰레드를 detach 
    // 시켜준다. 
    pthread_detach(p_thread);
    pause();
    return 0;
}
			
위의 쏘쓰 코드에서 detach 시켰을때와 그렇지 않았을때의 메모리 상황을 비교해보기 바란다. detatach 를 했을경우 프로세스의 메모리 사용율과 detache 를 주석 처리했을경우의 메모리 사용율의 변화를 서로 비교해보면 되는데, detach 를 사용하지 않았을경우 t_function 이 종료가 되더라도 자원이 해제되지 않음을 볼수 있을것이다. 테스트는 간단한 스크립트를 이용하도록 한다.
[root@localhost test]# while [ 1 ]; do ps -aux | grep pthread | grep -v grep | grep -v vim; sleep 1; done
root      2668  0.0  0.1  1436  292 pts/8    S    18:37   0:00 ./pthread_detach
root      2668  0.0  0.1  1436  292 pts/8    S    18:37   0:00 ./pthread_detach
			
위의 ps 내용에서 5번째 필드의 변화를 확인하면 된다.


2.4. pthread_exit

void pthread_exit(void *retval);
			
pthread_exit 는 현재 실행중인 쓰레드를 종료시키고자 할때 사용한다. 만약pthread_cleanup_push 가 정의되어 있다면, pthread_exit 가 호출될경우 cleanup handler 가 호출된다. 보통 이 cleanup handler 은 메모리를 정리하는 등의 일을 하게 된다.

예제 : pthread_exit.c

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

// 쓰레드 함수
// 1초를 기다린후 아규먼트^2 을 리턴한다.
void *t_function(void *data)
{
    int num = *((int *)data);
    int i = 0;
    while(1)
    {
        if (i == 3)
            pthread_exit(0);
        printf("loop %d\n", i);
        i++;
        sleep(1);
    }
}

int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;


    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    pthread_join(p_thread, (void **)&status);
    return 0;
}
			


2.5. pthread_cleanup_push

void pthrad_cleanup_push(void (*routine) (void *), void *arg);
			
이것은 cleanup handlers 를 인스톨하기 위해서 사용된다. pthread_exit(3) 가 호출되어서 쓰레드가 종료될때 pthread_cleanup_push 에 의해서 인스톨된 함수가 호출된다. routine이 쓰레드가 종료될때 호출되는 함수이다. arg는 아규먼트이다.

cleanup handlers 는 주로 자원을 되돌려주거나, mutex 잠금등의 해제를 위한 용도로 사용된다. 만약 mutex 영역에서 pthread_exit 가 호출되어 버릴경우 다른쓰레드에서 영원히 block 될수 있기 때문이다. 또한 malloc 으로 할당받은 메모리, 열린 파일지정자를 닫기 위해서도 사용한다.

예제 : pthread_cleanup.c

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>

// 쓰레드 함수
// 1초를 기다린후 아규먼트^2 을 리턴한다.
//

char *mydata;
void cleanup(void *);
void *t_function(void *data)
{
    int num = *((int *)data);
    int i = 0;
    int a = 1;
    // cleanup handler 로 cleanup 함수를 
    // 지정한다. 
    pthread_cleanup_push(cleanup, (void *)&a);
    mydata = (char *)malloc(1000);
    while(1)
    {
        if (i == 3)
        {
            // pthread_exit 가 호출되면서 
            // cleanup 을 호출하게 된다. 
            pthread_exit(0);
            return 1;
        }
        printf("loop %d\n", i);
        i++;
        sleep(1);
    }
    pthread_cleanup_pop(0);
}


int main()
{
    pthread_t p_thread;
    int thr_id;
    int status;
    int a = 100;


    thr_id = pthread_create(&p_thread, NULL, t_function, (void *)&a);
    if (thr_id < 0)
    {
        perror("thread create error : ");
        exit(0);
    }
    pthread_join(p_thread, (void **)&status);
    printf("Join finish\n");
}

// cleanup handler
void cleanup(void *myarg)
{
    printf("thread is clean up\n");
    printf("resource free\n");
    free(mydata);
}
			


2.6. pthread_cleanup_pop

pthread_cleanup_push 와 함께 사용되며, install 된 cleanup handler 을 제거하기 위해서 사용된다.

void pthread_cleanup_pop(int execute);
			
만약 execute 가 0 이라면, pthread_cleanup_push 에 의해 인스톨된 cleanup handler 를 (실행시키지 않고)삭제만 시킨다. 0 이 아닌 숫자라면 cleanup handler 을 실행시키고 삭제 된다. 사용예제는 2.5절을 참고하라.

그리고 pthread_cleanup_push 와 pthread_cleanup_pop 은 반드시 같은 함수내의 같은 레벨의 블럭에서 한쌍으로 사용해야 한다.


2.7. pthread_self

pthread_t pthread_self(void);
			
pthread_self를 호출하는 현재 쓰래드의 쓰레드식별자를 되돌려준다.

예제 : pthread_self.c

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

void *func(void *a)
{
    pthread_t id;
    id = pthread_self();
    printf("->%d\n", id);
}

int main(int argc, char **argv)
{
    pthread_t p_thread;
    pthread_create(&p_thread, NULL, func, (void *)NULL);
    printf("%d\n", p_thread);
    pthread_create(&p_thread, NULL, func, (void *)NULL);
    printf("%d\n", p_thread);

	return 1;
}
			


3. 쓰레드 동기화 함수

쓰레드 동기화와 관련된 함수들이다.


3.1. pthread_mutex_init

int pthread_mutex_init(pthread_mutex_t * mutex, 
           const pthread_mutex_attr *attr); 
			
mutex 는 여러개의 쓰레드가 공유하는 데이타를 보호하기 위해서 사용되는 도구로써, 보호하고자 하는 데이타를 다루는 코드영역을 단지 한번에 하나의 쓰레드만 실행가능 하도록 하는 방법으로 공유되는 데이타를 보호한다. 이러한 코드영역(하나의 쓰레드만 점유가능한)을 critical section 이라고 하며, mutex 관련 API 를 이용해서 관리할수 있다.

pthread_mutex_init 는 mutex 객체를 초기화 시키기 위해서 사용한다. 첫번째 인자로 주어지는 mutex 객체 mutex를 초기화시키며, 두번째 인자인 attr 를 이용해서 mutex 특성을 변경할수 있다. 기본 mutex 특성을 이용하기 원한다면 NULL 을 사용하면 된다.

mutex 특성(종류) 에는 "fast", "recurisev", "error checking" 의 종류가 있으며, 기본으로 "fast" 가 사용된다.

// 뮤텍스 객체 선언
pthread_mutex_t mutex_lock;
...
void *t_function()
{
    pthread_mutex_lock(&mutex_lock);
    // critical section
    pthread_mutex_unlock(&mutex_lock);
}
int main()
{
    pthread_t p_thread;
    int state;
    // 뮤텍스 객체 초기화, 기본 특성으로 초기화 했음
    pthread_mutex_init(&mutex_lock, NULL);
    pthread_create(&p_thread, NULL, t_function, (void *)&a);
    ...
    pthread_joinc(&p_thread, (void **)&status);
}
			


3.2. pthread_mutex_destory

int pthread_mutex_destory(pthread_mutex_t *mutex);
			
인자로 주어진 뮤텍스 객체 mutex 를 제거하기 위해서 사용된다. mutex  pthread_mutex_init()함수를 이용해서 생성된 뮤텍스 객체이다.

pthread_mutex_destory 를 이용해서 제대로 mutex 를 삭제하려면 이 mutex 는 반드시 unlock 상태이여야 한다.


3.3. pthread_mutex_lock

int pthread_mutex_lock(pthread_mutex_t *mutex);
			
pthread_mutex_lock 는 critcal section 에 들어가기 위해서 mutex lock 을 요청한다. 만약 이미 다른 쓰레드에서 mutex lock 를 얻어서 사용하고 있다면 다른 쓰레드에서 mutex lock(뮤텍스 잠금) 을 해제할때까지(사용할수 있을때까지) 블럭 된다.

만약 다른 어떤 쓰레드에서도 mutex lock 을 사용하고 있지 않다면, 즉시 mutex lock 을 얻을수 있게 되고 critcal section 에 진입하게 된다. critcal section 에서의 모든 작업을 마쳐서 사용하고 있는 mutex lock 이 더이상 필요 없다면 pthread_mutex_unlock 를 호출해서 mtuex lock 를 되돌려준다.


3.4. pthread_mutex_unlock

int pthread_mutex_unlock(pthread_mutex_t *mutex); 
			
critical section 에서의 모든 작업을 마치고 mutex lock 을 돌려주기 위해서 사용한다. pthread_mutex_unlock 를 이용해서 mutex lock 를 되돌려주면 다른 쓰레드에서 mutex lock 를 얻을수 있는 상태가 된다.


3.5. pthread_cond_init

int pthread_cond_init(pthread_cond_t *cond, 
                    const pthread_cond_attr *attr);
			
pthread_cond_init는 조견변수 (condition variable)cond를 초기화하기 위해서 사용한다. attr 를 이용해서 조건변수의 특성을 변경할수 있으며, NULL 을 줄경우 기본특성으로 초기화된다.

조건변수 cond는 상수 PTHREAD_COND_INITIALIZER 을 이용해서도 초기화 할수 있다. 즉 다음과 같은 2가지 초기화 방법이 존재한다.

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
or
pthread_cond_init(&cond, NULL);
			


3.6. pthread_cond_signal

int pthread_cond_signal(pthread_cond_t *cond);
			
조건변수 cond에 시그날을 보낸다. 시그날을 보낼경우 cond에서 기다리는(wait) 쓰레드가 있다면 쓰레드를 깨우게 된다(봉쇄가 풀림). 만약 조건변수 cond를 기다리는 쓰레드가 없다면, 아무런 일도 일어나지 않게되며, 여러개의 쓰레드가 기다리고 있다면 그중 하나의 쓰레드에게만 전달된다. 이때 어떤 쓰레드에게 신호가 전달될지는 알수 없다.


3.7. pthread_cond_boradcast

int pthread_cond_broadcast(pthread_cond_t *cond);
			
조건변수 cond에서 기다리는(wait) 모든 쓰레드에게 신호를 보내서, 깨운다는 점을 제외하고는pthread_cond_signal과 동일하게 작동한다.


3.8. pthread_cond_wait

int pthread_cond_wait(pthread_cond_t cond, pthread_mutex_t *mutex); 
			
조건변수 cond를 통해서 신호가 전달될때까지 블럭된다. 만약 신호가 전달되지 않는다면 영원히 블럭될수도 있다. pthread_cond_wait는 블럭되기 전에 mutex 잠금을 자동으로 되돌려준다.


3.9. pthread_cond_timewait

int pthread_cond_timedwait(pthread_cont_t *cond, pthread_mutex_t *mutex, 
                           const struct timespec *abstime);
			
조건변수 cond를 통해서 신호가 전달될때까지 블럭되며 자동으로 mutex을 돌려주는 점에서는pthread_cond_wait와 동일하다. 그러나 시간체크가 가능해서 abstime시간동안 신호가 도착하지 않는다면 error 를 발생하면서 리턴한다. 이때 리턴값은 ETIMEDOUT 이다. errno 가 세팅되는게 아닌, 리턴값으로 에러가 넘어오는것에 주의해야 한다.

또한 pthread_cond_timedwait함수는 다른 signal 에 의해서 interrupted 될수 있으며 이때 EINTR 을 리턴한다. 이 함수를 쓸때는 interrupted 상황에 대한 처리를 해주어야 한다.


3.10. pthread_cond_destroy

int pthread_cond_destroy(pthread_cond_t *cond);
			
pthread_cond_init를 통해서 생성한 조건변수cond에 대한 자원을 해제한다. destroy 함수를 호출하기 전에 어떤 쓰레드도 cond에서의 시그널을 기다리지 않는걸 확인해야 한다. 만약 cond 시그널을 기다리는 쓰레드가 존재한다면 이 함수는 실패하고 EBUSY 를 리턴한다.


3.11. 예제코드

이번장에서 설명한 쓰레드 동기화 관련 함수의 이해를 돕기 위해서 간단한 예제를 준비했다. 설명은 주석으로 대신한다.

예제 : pthrad_sync_api.c

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

using namespace std;

void *ping(void *);
void *pong(void *);

pthread_mutex_t sync_mutex;
pthread_cond_t  sync_cond;

pthread_mutex_t gmutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t  gcond  = PTHREAD_COND_INITIALIZER;

int main()
{
    vector<void *(*)(void *)> thread_list;
    vector<pthread_t> tident(10); 
    int thresult;
    int status;
    int i;

    pthread_mutex_init(&sync_mutex, NULL);
    pthread_cond_init(&sync_cond, NULL);

    thread_list.push_back(pong);
    thread_list.push_back(ping);

    for(i = 0; i < thread_list.size(); i++ )
    {
        pthread_mutex_lock(&sync_mutex);
        if (pthread_create(&tident[i], NULL, thread_list[i], (void *)NULL) <0)
        {
            perror("error:");
            exit(0);
        }
        pthread_cond_wait(&sync_cond, &sync_mutex);
        pthread_mutex_unlock(&sync_mutex);
    }
    for (i = 0; i < tident.size(); i++)
    {
        pthread_join(tident[i], (void **)&status);
    }
}

void *ping(void *data)
{
    int i=0;
    pthread_mutex_lock(&sync_mutex);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        printf("%d : ping\n", i);
        pthread_cond_signal(&gcond);
        pthread_cond_wait(&gcond, &gmutex);
        pthread_mutex_unlock(&gmutex);
        usleep(random()%100);
        i++;
    }
}

void *pong(void *data)
{
    int i = 0;
    pthread_mutex_lock(&sync_mutex);
    sleep(1);
    pthread_cond_signal(&sync_cond);
    pthread_mutex_unlock(&sync_mutex);
    while(1)
    {
        pthread_mutex_lock(&gmutex);
        pthread_cond_wait(&gcond, &gmutex);
        printf("%d : pong\n", i);
        pthread_cond_signal(&gcond);
        pthread_mutex_unlock(&gmutex);
        i++;
    }
}
			

위의 예제는 ping&pong 프로그램으로 ping 쓰레드와 pong 쓰레드가 각각 번갈아가면서 "ping", "pong" 을 날리는 프로그램이다. 2개의 영역에 걸쳐서 크리티컬섹션이 지정되어 있으며 각 크리티컬섹션안에는 쓰레드 동기화를 위해서 ptread_cond_signal 이 쓰여지고 있다.

위의 코드는 기본적으로 pong 쓰레드가 먼저 시그널을 대기하고 있다가 그 후 ping 쓰레드가 진입해서 "ping"을 날리고 시그널을 발생시키면 "pong" 메시지를 발생시키도록 되어 있다. 그렇다면 while 문에 있는 크리티컬 섹션에 반드시 pong 쓰레드가 먼저 진입할수 있도록 만들어줘야 할것이다. 그래서 위의 코드에서는 pong 쓰레드를 먼저 생성시켰다. 그러나 이것만으로는 충분하지 않다. 예를들어서 pong 쓰레드에서 크리티컬섹션에 들어가기 위해서 어떤 부가적인 작업이 있다고 했을때(메모리초기화, 기타 다른 함수 호출과 같은, 위에서는 sleep 으로 대신했다), 우리가 의도했던 바와는 다르게 ping 가 먼저 크리티컬섹션에 진입할수도 있다. 이럴경우 2개의 쓰레드는 교착상태에 빠지게 된다.

ping 쓰레드가 크리티컬섹션에 먼저 진입했을경우 ping 쓰레드는 "ping" 출력시키고 시그널을 발생시킬 것이고 pong 쓰레드가 "pong"를 출력시키고 시그널을 발생시킬때까지 시그널대기 하게 된다. ping 쓰레드가 시그널대기 하게 되면, 크리티컬섹션에 대한 뮤텍스 잠금이 해제됨으로 뒤늦게 크리티컬섹셔네 진입을 시도하던 pong 가 크리티컬섹션에 진입하고 ping 쓰레드에서부터 신호가 있는지 기다리게 될것이다. 그러나 ping 쓰레드는 이미 신호를 날려버렸음으로, pong 쓰레드는 결코 도착하지 않을 신호를 기다리며 영원히 시그널대기 하게 될것이다. 이런식으로 2개의 쓰레드는 교착상태에 빠져 버린다.

이 문제는 쓰레드간 동기화를 이용해서 해결할수 있으며, 위 코드에서는 mutex 잠금과, 조건변수를 이용해서 해결하고 있다. 물론 쓰레드간 동기화를 위해서 사용할수 있는 원시?적인 방법으로 sleep 나 usleep 같은 함수를 호출하는 방법도 있긴 하지만, ping 쓰레드에서 크리티컬 섹션에 진입하기전 1초 정도 sleep 을 주는 식으로 사용가능하지만 추천할만하진 않다. (간혹 간단하게 사용할수는 으며, 가장 확실한 방법을 제공해 주기도 한다)


4. Thread Attribute 함수

4.1. pthread_attr_init

int pthread_attr_init(pthread_attr_t *attr);
			
pthread_attr_init는 thread attribute 객체인 attr을 디폴트 값으로 초기화 시킨다.

성공할경우 0을 돌려주고 실패할경우 -1 을 되돌려준다.


4.2. pthread_attr_distroy

int pthread_attr_destroy(pthread_attr_t *attr);
			
pthread_attr_init에 의해 생성된 thread attribute 객체인 attr을 제거한다. 제거된 attr 을 다시 사용하기 위해서는 pthread_attr_init를 이용해서 다시 초기화 해주어야 한다.


4.3. pthread_attr_getscope

int pthread_attr_getscope(const pthread_attr_t *attr, 
             int *scope);
			
쓰레드가 어떤 영역(scope)에서 다루어지고 있는지를 얻어오기 위해서 사용된다. PTHREAD_SCOPE_SYSTEM과 PTHREAD_SCOPE_PROCESS 의 2가지 영역중에 선택할수 있다. SYSTEM 영역 쓰레드는 user 모드 쓰레드라고 불리우며, PROCESS 쓰레드는 커널모드 쓰레드라고 불리운다. 리눅스의 경우 유저모드 쓰레드인데, 즉 커널에서 쓰레드를 스케쥴링하는 방식이 아닌 쓰레드 라이브러리를 통해서 쓰레드를 스케쥴링 하는 방식을 사용한다.

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


int main()
{
    pthread_attr_t pattr;
    int scope;

    pthread_attr_init(&pattr);

    pthread_attr_getscope(&pattr, &scope);
    if (scope == PTHREAD_SCOPE_SYSTEM)
    {
        printf("user mode thread\n");
    }
    else if (scope ==  PTHREAD_SCOPE_PROCESS)
    {
        printf("Kernel mode thread\n");
    }

    return 1;
}
			
위 프로그램을 컴파일한후 Linux 에서 실행시키면 "user mode thread"를 출력하고 솔라리스 상에서 실행시키면 "kernel mode thread"를 출력한다.


4.4. pthread_attr_setscope

int pthread_attr_setscope(pthread_attr_t *attr, int scope);
			
쓰레드가 어떤 영역(scope)에서 작동하게 할것인지 결정하기 위해서 사용한다. 리눅스의 경우 Kernel mode 쓰레드를 지원하지 않음으로 오직 PTHREAD_SCOPE_SYSTEM 만을 선택할수 있다. 반면 솔라리스는 유저모드와 커널모드중 선택이 가능하다.

pthread_attr_setscope.c

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


int main()
{
    pthread_attr_t pattr;
    int scope;

    pthread_attr_init(&pattr);

    pthread_attr_setscope(&pattr, PTHREAD_SCOPE_PROCESS);
    pthread_attr_getscope(&pattr, &scope);
    if (scope == PTHREAD_SCOPE_SYSTEM)
    {
        printf("user mode thread\n");
    }
    else if (scope ==  PTHREAD_SCOPE_PROCESS)
    {
        printf("Kernel mode thread\n");
    }

    return 1;
}
			
위코드에서 쓰레드가 커널 모드에서 작동하도록 지정을 했다. 리눅스에서 실행시킬경우에는 비록 커널모드로 지정을 했다고 하더라도 유저모드 쓰레드로 작동하게 된다. 솔라리스의 경우에는 setscope 로 지정한대로 커널모드에서 작동하게 된다.


4.5. pthread_attr_getdetachstate

int pthread_attr_getdetachstate(pthread_attr_t *attr,
           int detachstate);
			
쓰레드가 join 가능한 상태(PTHREAD_CREATE_JOINABLE) 인지 detached 상태인지 (PTHREAD_CREATE_DETACHED) 인지를 알아낸다. 알아낸 값은 아규먼트 detachstate 에 저장된다.

기본은 PTHREAD_CREATE_JOINABLE 이며, pthread_detach를 이용해서 생성된 쓰레드를 detach 상태로 만들었을경우 또는 pthread_attr_setdetachstate함수를 이용해서 쓰레드를 detache 상태로 변경시켰을경우 PTHREAD_CREATE_DETACHED 상태가 된다.

예제 : pthread_attr_getdetachstate.c

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

pthread_attr_t attr;
void *test(void *a)
{
    int policy;
    printf("Thread Create\n");
    pthread_attr_getdetachstate(&attr, &policy);
    if (policy == PTHREAD_CREATE_JOINABLE)
    {
        printf ("Join able\n");
    }
    else if (policy == PTHREAD_CREATE_DETACHED)
    {
        printf ("Detache\n");
    }
}
int main()
{
    int status;
    pthread_t p_thread;
    pthread_attr_init(&attr);
    if (pthread_create(&p_thread, NULL, test, (void *)NULL) < 0)
    {
        exit(0);
    }

    pthread_join(p_thread, (void **)&status);
}
			
위의 프로그램을 실행시키면 분명 "Join able"를 출력할것이다.


4.6. pthread_attr_setdetachstate

int  pthread_attr_setdetachstate(pthread_attr_t *attr, 
             int detachstate);
			
쓰레드의 상태를 PTHREAD_CREATE_JOINABLE 혹은 PTHREAD_CREATE_DETACHED 상태로 변경시키기 위해서 사용된다. 아래와 같은 방법으로 사용하면 된다.
pthread_attr_t attr;
...
// JOINABLE 상태로 변경하고자 할때 
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
// DETACHED 상태로 변경하고자 할때
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
			


5. 쓰레드 시그널 관련

쓰레드간 프로세스와 쓰레드간 시그널 전달관련 API들이다. 자세한 내용은 쓰레드와 시그널을 참고하기 바란다.


5.1. pthread_sigmask

#include <pthread.h>
#include <signal.h>

int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);
			
쓰레드에서 시그널은 서로 공유된다. 그런이유로 만약 프로세스에 시그널이 전달되면 프로세스가 생성된 모든 쓰레드로 시그널이 전달된다. 그러나 특정 쓰레드만 시그널을 받도록 하고 싶을 때가 있을 것이다. 이경우 이 함수를 이용하면 된다.


5.2. pthread_kill

#include <pthread.h>
#include <signal.h>

int pthread_kill(pthread_t thread, int signo);
			
쓰레드 식별번호 thread signo 번호의 시그널을 전달한다.


5.3. sigwait

#include <pthread.h>
#include >signal.h>

int sigwait(const sigset_t *set, int *sig);
			
시그널 전달을 동기적으로 기다린다.


6. 쓰레드 취소

자세한 내용은 쓰레드 취소와 종료 pthread_cancel(3)을 참고하기 바란다. 여기에서는 인덱스만 제공한다.


6.1. pthread_cancel

#include <pthread.h>

int pthread_cancel(pthread_t thread);
			


6.2. pthread_setcancelstate

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);
			


6.3. pthread_setcancelstate

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);
			


6.4. pthread_setcanceltype

#include <pthread.h>

int pthread_setcanceltype(int type, int *oldtype);
			


6.5. pthread_testcancel

#include <pthread.h>

void pthread_testcancel(void);
			

참고 문서 #

  1. Pthread 뮤텍스와 조건변수
  2. pthread 1
  3. pthread 모델
  4. pthread 3
Posted by 두장
이전버튼 1 이전버튼