database.sarang.net
UserID
Passwd
Database
ㆍDBMS
MySQL
PostgreSQL
Firebird
Oracle
Informix
Sybase
MS-SQL
DB2
Cache
CUBRID
LDAP
ALTIBASE
Tibero
DB 문서들
스터디
Community
공지사항
자유게시판
구인|구직
DSN 갤러리
도움주신분들
Admin
운영게시판
최근게시물
DBMS Devel 461 게시물 읽기
 News | Q&A | Columns | Tutorials | Devel | Files | Links
No. 461
리눅스 쓰레드 프로그래밍
작성자
정재익(advance)
작성일
2002-07-22 06:06
조회수
14,569

출처 : http://kldp.org/Translations/html/Thread_Programming-KLDP/Thread_Programming-KLDP-2.html

 

-----------------------------------------------

-----------------------------------------------

 

1. 약간의 이론

 

 

1.1 소개

 

LinuxThreads는 다중 쓰레드 프로그래밍을 위한 리눅스 라이브러리이다. LinuxThreads는 커널 수준의 쓰레드를 제공한다; 쓰레드들은 clone() 시스템콜에 의해 만들어지고 모든 스케줄링은 커널에서 이루어진다. Posix 1003.1c API를 구현하였고 커널 2.0.0이상의 커널과 적절한 C 라이브러리를 가지고 있는 어떠한 리눅스 시스템에 동작한다.

 

 

 

1.2 쓰레드란 무엇인가?

 

쓰레드는 프로그램을 통한 제어의 순차적인 흐름이다. 그래서 다중 쓰레드 프로그래밍은 여러 제어 쓰레드가 한 프로그램에서 동시에 수행하는 병렬 프로그래밍의 한 형태이다.

 

다중 쓰레드 프로그래밍은 모든 쓰레드가 같은 메모리 공간을 (그리고 파일 디스크립터와 같은 일부 시스템 자원들을) 공유하는 유닉스 스타일의 다중 프로세싱과는 다르다. 대신에 유닉스의 프로세스와 같이 자신만의 고유 메모리상에 동작한다. 그래서 한 프로세스의 두 쓰레드 사이의 문맥 교환(context switch)는 두 프로세스 사이의 문맥 교환보다 굉장한 수월 하다.

 

쓰레드를 사용하는 두 가지 주요한 이유가 있다:

 

 

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

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

1.3 원자성(atomicity)과 휘발성(volatility)

 

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

 

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

 

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

 

 

 

1.4 Lock (잠금)

 

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

 

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

 

 

i 변수에 대해 lock을 건다.

잠긴 변수의 값을 수정한다.

lock을 제거한다.

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

 

 

2. 그리고 약간의 실제

 

 

 

2.1 pthread.h 헤더

 

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

 

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

 

 

공유 변수들에 lock을 걸고 쓰레드를 만들기 위한 pthread 루틴들을 사용한다.

쓰레드 서브 루틴에 넘겨야 할 모른 인자들을 포함하는 구조체를 만든다.

몇 가지 기본적인 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*D.y;

 

/* 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 = y = i;

 

/* lock 변수를 초기화한다. */

pthread_mutex_init(&s_lock, NULL);

 

for (i = 0; i < cpu; i++) {

/* 구조체를 초기화한다. */

A.n = i; /* 쓰레드의 수 */

A.x = x;

A.y = y;

A.l = L;

A.nproc = cpu; /* CPU의 수 */

A.p_s = &s;

A.p_s_lock = &s_lock;

 

if (pthread_create(&thread, NULL, SMP_scalprod,

&A)) {

fprintf(stderr, "%s: cannot make thread\n",

argv[0]);

exit(1);

}

}

 

for (i = 0; i < cpu; i++) {

if (pthread_join(thread, &retval)) {

fprintf(stderr, "%s: cannot join thread\n",

argv[0]);

exit(1);

}

}

 

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

exit(0);

}

[Top]
No.
제목
작성자
작성일
조회
1068SQLite 와 PHP의 연결 [1]
정재익
2005-01-03
16414
461리눅스 쓰레드 프로그래밍
정재익
2002-07-22
14569
177멀티미디어 데이터베이스 기술
정재익
2001-12-14
19076
133비트 파워프로젝트/자동차보험사의 데이터 마이닝 시스템 구축
정재익
2001-12-06
18462
Valid XHTML 1.0!
All about the DATABASE... Copyleft 1999-2020 DSN, All rights reserved.
작업시간: 0.010초, 이곳 서비스는
	PostgreSQL v13.0으로 자료를 관리합니다