다음 이전 차례

2. 그리고 약간의 실제

2.1 pthread.h 헤더

LinuxThreads가 제공하는 것은 쓰레드 루틴들의 프로토타입을 선언하는 /usr/include/pthread.h 헤더를 통해서 이용 가능하다.

다중 쓰레드 프로그램의 작성은 기본적으로 두 단계의 과정이다:

몇 가지 기본적인 pthread.h의 루틴들을 간단히 설명하면서 이 두 단계를 실펴보자.

2.2 lock의 초기화

해야만 하는 첫번째 행동들 중의 하나는 모든 lock들을 초기화하는 것이다. POSIX lock들은 pthread_mutex_t 타입의 변수로 선언된다; 각 lock을 초기화하기 위허 다음 루틴을 호출할 필요가 있다:

int pthread_mutex_init(pthread_mutex_t *mutex, const pthread
_mutexattr_t *mutexattr);

묶어서 보면:

#include <pthread.h>
...
        pthread_mutex_t lock;
        pthread_mutex_init(&lock, NULL);
...

pthread_mutex_init 함수는 mutex 인자가 가1르키는 mutex 객체를 mutexattr에 의해 명시된 mutex 속성에 따라 초기화를 한다. mutexattr의 NULL이면, 디폴트 속성이 사용된다.

계속헤서 이 초기화된 lock들을 어떻게 사용하는지 보겠다.

2.3 쓰레드 생성하기

POSIX는 각 쓰레드를 나타내기 위해 사용자가 pthread_t 타입의 변수를 선언하도록 한다. 다음 호출로 쓰레드가 생성된다:

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

성공한다면 새로이 생성된 쓰레드의 id가 thread 인자가 지시한 영역에 정장이 되고 0인 리턴된다. 에러가 발생하면 0이 아닌 값이 리턴된다.

f() 루틴을 수행하는 쓰레드를 만들고 f()에 arg 변수를 가르키는 포이터 를 넘기기 위해서는 다음과 같이 한다:

#include <pthread.h>
...
        pthread_t thread;
        pthread_create(&thread, NULL, f, &arg).
...

f() 루빈은 다음과 같은 프로토타입을 가져야 한다:

void *f(void *arg);

2.4 깨끗한 종료

마지막 단계로 f() 루틴의 결과를 접근하기 전에 만든 모든 쓰레드가 종료 할 때까지 기다려야 한다. 다음을 호출한다:

int pthread_join(pthread_t th, void **thread_return);

th가 가르키는 쓰레드가 종료할 때까지 위의 함수를 호출한 쓰레드의 수행 을 멈춘다. 만약 thread_return이 NULL이니면 th의 리턴값은 thread_return이 가리키 는 영역에 저장된다.

2.5 쓰레드 루틴에 데이터 전달하기

호출한 루틴의 정보를 쓰레드 루틴에 넘기는 두 가지 방법이 있다:

두번째 것이 코드의 모듈성을 보전하는 데 가장 좋은 선택이다. 구제체는 세 가지 단계의 정보를 포함해야 한다; 첫째로 공유 변수들과 lock들에 관한 정보, 두번째로 루틴에서 필요로 하는 모든 데이터에 대한 정보, 세번째로 쓰래들를 구분해주는 id와 쓰레드가 이용할 수 있는 CPU의 수에 대한 정보 (런타임에 이 정보를 제공하는 것이 더 쉽다). 구조체의 첫번째 요소을 살펴보자; 넘겨진 정보는 모든 쓰레드들 사이의 공유도늰 것이 어야한다. 그래서 필요한 변수들과 lock들의 포인터를 사용 해야 한다. double 타입의 공유 변수 var와 그 에 대한 lock을 넘기기 위해 구조체는 두 멤버 변수를 가져야만 한다:

double volatile *var;
pthread_mutex_t *var_lock;

volatile 속성의 사용 위치에 주목하라. 이는 포인터 자체가 아니라 var가 volatile임을 나타낸다.

2.6 병렬 코드의 예

쓰래들를 이용하여 쉽게 병렬화를 할 수 있는 프로그램의 예는 두 벡터의 스칼라코곱을 계산이다. 주석을 붙인 코드를 제시한다.

/* 컴파일 하려면 gcc  -D_REENTRANT -lpthread */

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

/* 알맞은 구조체 선언 */ 
typedef struct {
        double volatile *p_s;      /* 스칼라 곱의 공유 변수 */
        pthread_mutex_t *p_s_lock; /* 변수 s의 lock */
        int n;                     /* 쓰레드의 수 */
        int nproc;                 /* 이용할 수 있는 프로세서의 수 */
        double *x;                 /* 첫번째 벡터의 데이터 */
        double *y;                 /* 두번째 벡터의 데이터 */
        int l;                     /* 벡터의 길이 */
} DATA;

void *SMP_scalprod(void *arg)
{
        register double localsum;
        long i;
        DATA D = *(DATA *)arg;

        localsum = 0.0;

        /* 각 쓰레드는 i = D.n에서 부터 스칼라 곱을 시작한다. 
           D.n = 1, 2, ...
           D.nproc 값을 갖는다. 정확히 D.nproc개의 쓰레드가 있기 
           때문에 i의 증가 같은 D.nproc이다. */

        for(i = D.n; i < D.l; i += D.nproc)
        localsum += D.x[i]*D.y[i];

        /* s에 대한 lock을 건다 ... */
        pthread_mutex_lock(D.p_s_lock);

        /* ... s의 값을 바꾼다. ... */
        *(D.p_s) += localsum;

        /* ... 그리고 lock를 제거한다. */
        pthread_mutex_unlock(D.p_s_lock);

        return NULL;
}

#define L 9     /* 벡터의 차원 */

int main(int argc, char **argv)
{
        pthread_t *thread;
        void *retval;
        int cpu, i;
        DATA *A;
        volatile double s = 0; /* 공유 변수 */ 
        pthread_mutex_t s_lock; 
        double x[L], y[L];

        if (argc != 2) {
                printf("usage: %s <number of CPU>\n", argv[0]);
                exit(1);
        }
        
        cpu = atoi(argv[1]);
        thread = (pthread_t *) calloc(cpu, sizeof(pthread_t));
        A = (DATA *) calloc(cpu, sizeof(DATA));

 
        for (i = 0; i < L; i++)
        x[i] = y[i] = i;

        /* lock 변수를 초기화한다. */
        pthread_mutex_init(&s_lock, NULL);

        for (i = 0; i < cpu; i++) {
                /* 구조체를 초기화한다. */
                A[i].n = i; /* 쓰레드의 수 */
                A[i].x = x;
                A[i].y = y;
                A[i].l = L;
                A[i].nproc = cpu; /* CPU의 수 */
                A[i].p_s = &s;
                A[i].p_s_lock = &s_lock;

                if (pthread_create(&thread[i], NULL, SMP_scalprod, 
                    &A[i])) {
                        fprintf(stderr, "%s: cannot make thread\n", 
                                argv[0]);
                        exit(1);
                }
        }

        for (i = 0; i < cpu; i++) {
                if (pthread_join(thread[i], &retval)) {
                        fprintf(stderr, "%s: cannot join thread\n", 
                                argv[0]);
                        exit(1);
                }
        }

        printf("s = %f\n", s);
        exit(0);
}

Copyright ⓒ 1999, Matteo Dell'Omodarme

Published in Issue 48 of Linux Gazette, December 1999


다음 이전 차례