· KLDP.org · KLDP.net · KLDP Wiki · KLDP BBS ·
주소록_basicdesign

<!doctype linuxdoc system> <!-- Generated by txt2sgml 0.1 -->

<article>

<title>주소록 기본 설계서 <author>wsong<htmlurl url="Mwsong@gmail.com" name="wsong@gmail.com"> <tdate>2008년 9월 4일 <abstract>


</abstract> <toc>

<sect> 서문<p>

<sect1> 어플리케이션의 목적<p> 이 어플리케이션은 프로그래밍의 연습을 위해 작성된다. 주소록의 기능은 콘솔 화면에서 동작한다.

<sect1> 개발 플랫폼<p> 동일 어플리케이션을 다양한 플랫폼용으로 컨버젼한다.첫버전의 어플리케이션은 Linux용이며, gcc컴파일러를 사용하여 컴파일 될것이다.Solaris환경을 에서는 Sun gcc컴파일이 사용되며, Windows에서는 Visual Studio를 개발환경으로 사용할 것이다.

<sect> 작성의 흐름<p>

<sect1> LINUX 환경호환 어플리케이션 작성<p>

<sect1> SOLARIS 환경호환 어플리케이션 작성<p>

<sect1> WINDOWS 환경호환 어플리케이션 작성<p>


<itemize> <item> 어떤 프로그램들은 하나의 제어 흐름 보다는 서로 통신하는 여러
쓰레드로 작성될 때만 최고의 성능을 낼 수 있다. (즉, 서버들)

<item> 다중 프로세서 시스템에서, 쓰레드들은 여러 프로세서상에서 병렬적
으로 수행될 수 있다. 이는 한 프로그램이 다른 프로세서에 작업을 분배할 수 있게 해준다. 이런 프로그램은 한 번에 한 CPU만을 사용할 수 있는 단일 쓰레드 프로그램 보다 훨씬 더 빠르다.


</itemize> <sect1> 원자성(atomicity)과 휘발성(volatility) <p>

쓰레드에 의해 공유되는 메모리를 접근하는 데는 더 주의가 필요하다. 병렬 프로그램은 일반적인 지역 메모리처럼 공유 메모리 객체를 접근할 수 없기 때문이다.

원자성(atomicity)는 어떤 객체에 대한 연산은 분리될 수 없는, 인터럽트 되는 않는 과정으로 이루어져야 되다는 개념을 말한다. 공유 메모리상의 데이터에 대한 연산은 원자적으로 이루어질 수 없다. 게다가 GCC 컴파일러 는 종종 레지스터에 공유 변수들의 값을 버퍼링하는 최적화를 수행할 것 이다. 이렇게 메모리 연산을 피하는 것이라도 모든 프로세서가 공유 데이터의 값이 변경된 것은 알 수 있어야만 한다.

레지스터에 공유 메모리의 값을 버퍼링하는 GCC의 최적화를 막기 위해 공유 메모리 상의 모든 객체는 volatile 속성의 타입으로 선언되어야 한다. 단 한 word의 volatile 객체를 읽고 쓸는 것은 원자적으로 이루어 지기 때문이다.


<sect1> Lock (잠금) <p>

결과값을 읽어오기 저장하는 것은 독립된 메모리 연산이다: ++i은 항상 공유 메모리 상의 i을 1만큼 증가시키지는 않는다. 두 연산 사이에 다른 프로세서가 i을 접근할 수 있기 때문이다. 그래서 두 프로세스가 둘 다 ++i을 수행한다면 2가 아닌 1만을 증가될 수도 있다.

그래서 한 쓰레드가 변수의 값을 바꾸는 동안은 다른 쓰레드가 그 변수에 대한 작업을 할 수 없게 하는 시스템 콜이 필요하다. 이는 아래 설명한 lock 방법으로 구현된다. 공유 변수의 값을 바꾸는 루틴을 수행하는 두 쓰레드가 있다고 가정을 하자. 그 루틴이 정확한 결과를 얻기 위해서는 다음과 같이 해야 한다.

<itemize> <item> i 변수에 대해 lock을 건다. <item> 잠긴 변수의 값을 수정한다. <item> lock을 제거한다. </itemize>

한 변수에 대한 lock이 걸릴 때 그 lock을 건 쓰레드만이 그 값을 바꿀 수 있다. 잠근 때문에 다른 쓰레드들은 블럭이 될 것이다. 한 변수에 대해서 는 한 번에 하나의 lock만이 허용되기 때문이다. ㅍ첫번째 쓰레드가 lock 을 제거할 때만 두번째 쓰레드가 lock을 걸 수 있다. 그 결과 공유 변수를 이용하는 것은 다른 프로세서들의 활동을 느리게 할 지도 모든다. 하지만 일반적인 참조는 지역 캐시를 이용한다.


<sect> 그리고 약간의 실제<p>


<sect1> pthread.h 헤더 <p>

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

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

<itemize> <item> 공유 변수들에 lock을 걸고 쓰레드를 만들기 위한 pthread 루틴들을
사용한다.
<item> 쓰레드 서브 루틴에 넘겨야 할 모른 인자들을 포함하는 구조체를
만든다.
</itemize>

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

<sect1> lock의 초기화 <p>

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

<tscreen><verb> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread _mutexattr_t *mutexattr); </verb></tscreen>

묶어서 보면:

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

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

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


<sect1> 쓰레드 생성하기 <p>

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

<tscreen><verb> int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg); </verb></tscreen>

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

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

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

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

<tscreen><verb> void *f(void *arg); </verb></tscreen>


<sect1> 깨끗한 종료 <p>

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

<tscreen><verb> int pthread_join(pthread_t th, void **thread_return); </verb></tscreen>

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


<sect1> 쓰레드 루틴에 데이터 전달하기 <p>

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

<itemize> <item> 전역 변수 <item> 구조체 </itemize>

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

<tscreen><verb> double volatile *var; pthread_mutex_t *var_lock; </verb></tscreen>

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


<sect1> 병렬 코드의 예 <p>

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

<tscreen><verb> /* 컴파일 하려면 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.xi*D.yi;

/* 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 xL, yL;

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

for (i = 0; i < L; i++) xi = yi = i;

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

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

if (pthread_create(&threadi, NULL, SMP_scalprod,
&Ai)) {
fprintf(stderr, "%s: cannot make thread\n",
argv0);
exit(1);
}
}

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

printf("s = %f\n", s); exit(0);
} </verb></tscreen>



</article>



sponsored by andamiro
sponsored by cdnetworks
sponsored by HP

Valid XHTML 1.0! Valid CSS! powered by MoniWiki
last modified 2008-09-04 00:29:48
Processing time 0.0106 sec