다음 이전 차례

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 인자가 가르키는 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 쓰레드 루틴에 데이터 전달하기

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

두번째 것이 코드의 모듈성(modularity)을 보전하는 데 가장 좋은 선택이다. 구제체는 세 가지 단계의 정보를 포함해야 한다; 첫째로 공유 변수들과 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


다음 이전 차례