주소록_basicdesign
<!doctype linuxdoc system>
<!-- Generated by txt2sgml 0.1 -->
<article>
<title>주소록 기본 설계서
<author>wsong<htmlurl url="
![]() </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의 값을 바꾼다. ... */
#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, } 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); </article>
|
Put not your trust in money, but put your money in trust. |