Linux 2006/09/08 14:36
블로그 > hyperlancer님의 블로그 http://blog.naver.com/hyperlancer/120014817994 | ||
1. 약간의 이론 1.1 소개 LinuxThreads는 다중 쓰레드 프로그래밍을 위한 리눅스 라이브러리이다. LinuxThreads는 커널 수준의 쓰레드를 제공한다; 쓰레드들은 clone() 시스템콜에 의해 만들어지고 모든 스케줄링은 커널에서 이루어진다. Posix 1003.1c API를 구현하였고 커널 2.0.0이상의 커널과 적절한 C 라이브러리를 가지고 있는 어떠한 리눅스 시스템에 동작한다. 1.2 쓰레드란 무엇인가? 쓰레드는 프로그램을 통한 제어의 순차적인 흐름이다. 그래서 다중 쓰레드 프로그래밍은 여러 제어 쓰레드가 한 프로그램에서 동시에 수행하는 병렬 프로그래밍의 한 형태이다. 다중 쓰레드 프로그래밍은 모든 쓰레드가 같은 메모리 공간을 (그리고 파일 디스크립터와 같은 일부 시스템 자원들을) 공유하는 유닉스 스타일의 다중 프로세싱과는 다르다. 대신에 유닉스의 프로세스와 같이 자신만의 고유 메모리상에 동작한다. 그래서 한 프로세스의 두 쓰레드 사이의 문맥 교환(context switch)는 두 프로세스 사이의 문맥 교환보다 굉장한 수월하다. 쓰레드를 사용하는 두 가지 이유가 있다:
쓰레드에 의해 공유되는 메모리를 접근하는데는 더 주의가 필요하다. 병렬 프로그램은 일반적인 지역 메모리처럼 공유 메모리 객체를 접근할 수 없기 때문이다. 원자성(atomicity)는 어떤 객체에 대한 연산은 분리될 수 없는, 인터럽트 되는 않는 과정으로 이루어져야 된다는 개념을 말한다. 공유 메모리상의 데이터에 대한 연산은 원자적으로 이루어질 수 없다. 게다가 GCC 컴파일러 는 종종 레지스터에 공유 변수들의 값을 버퍼링하는 최적화를 수행할 것이다. 이렇게 메모리 연산을 피하는 것이라도 모든 프로세서가 공유 데이터의 값이 변경된 것은 알 수 있어야만 한다. 레지스터에 공유 메모리의 값을 버퍼링하는 GCC의 최적화를 막기 위해 공유 메모리 상의 모든 객체는 volatile 속성의 타입으로 선언되어야 한다. 단 한 word의 volatile 객체를 읽고 쓸는 것은 원자적으로 이루어지기 때문이다. 1.4 Lock (잠금) 결과값을 읽어오기 저장하는 것은 독립된 메모리 연산이다: ++i은 항상 공유 메모리 상의 i을 1만큼 증가시키지는 않는다. 두 연산 사이에 다른 프로세서가 i을 접근할 수 있기 때문이다. 그래서 두 프로세스가 둘 다 ++i을 수행한다면 2가 아닌 1만 증가될 수도 있다. 그래서 한 쓰레드가 변수의 값을 바꾸는 동안은 다른 쓰레드가 그 변수에 대한 작업을 할 수 없게 하는 시스템 콜이 필요하다. 이는 아래 설명한 lock 방법으로 구현된다. 공유 변수의 값을 바꾸는 루틴을 수행하는 두 쓰레드가 있다고 가정을 하자. 그 루틴이 정확한 결과를 얻기 위해서는 다음과 같이 해야 한다.
2. 그리고 약간의 실제 2.1 pthread.h 헤더 LinuxThreads가 제공하는 것은 쓰레드 루틴들의 프로토타입을 선언하는 /usr/include/pthread.h 헤더를 통해서 이용 가능하다. 다중 쓰레드 프로그램의 작성은 기본적으로 두 단계의 과정이다:
2.2 lock의 초기화 해야만 하는 첫번째 행동들 중의 하나는 모든 lock들을 초기화하는 것이다. POSIX lock들은 pthread_mutex_t 타입의 변수로 선언된다; 각 lock을 초기화하기 위허 다음 루틴을 호출할 필요가 있다: 묶어서 보면: pthread_mutex_init 함수는 mutex 인자가 가1르키는 mutex 객체를 mutexattr에 의해 명시된 mutex 속성에 따라 초기화를 한다. mutexattr의 NULL이면, 디폴트 속성이 사용된다. 계속해서 이 초기화된 lock들을 어떻게 사용하는지 보겠다. 2.3 쓰레드 생성하기 POSIX는 각 쓰레드를 나타내기 위해 사용자가 pthread_t 타입의 변수를 선언하도록 한다. 다음 호출로 쓰레드가 생성된다: 성공한다면 새로이 생성된 쓰레드의 id가 thread 인자가 지시한 영역에 저장되고 0인 리턴된다. 에러가 발생하면 0이 아닌 값이 리턴된다. f() 루틴을 수행하는 쓰레드를 만들고 f()에 arg 변수를 가르키는 포인터를 넘기기 위해서는 다음과 같이 한다: f() 루틴은 다음과 같은 프로토타입을 가져야 한다: 2.4 깨끗한 종료 마지막 단계로 f() 루틴의 결과를 접근하기 전에 만든 모든 쓰레드가 종료 할 때까지 기다려야 한다. 다음을 호출한다: th가 가르키는 쓰레드가 종료할 때까지 위의 함수를 호출한 쓰레드의 수행 을 멈춘다. 만약 thread_return이 NULL이니면 th의 리턴값은 thread_return이 가리키는 영역에 저장된다.2.5 쓰레드 루틴에 데이터 전달하기 호출한 루틴의 정보를 쓰레드 루틴에 넘기는 두 가지 방법이 있다:
두번째 것이 코드의 모듈성을 보전하는 데 가장 좋은 선택이다. 구조체는 세 가지 단계의 정보를 포함해야 한다; 첫째로 공유 변수들과 lock들에 관한 정보, 두번째로 루틴에서 필요로 하는 모든 데이터에 대한 정보, 세번째로 쓰레드를 구분해주는 id와 쓰레드가 이용할 수 있는 CPU의 수에 대한 정보 (런타임에 이 정보를 제공하는 것이 더 쉽다). 구조체의 첫번째 요소을 살펴보자; 넘겨진 정보는 모든 쓰레드들 사이의 공유된 것이어야한다. 그래서 필요한 변수들과 lock들의 포인터를 사용 해야 한다. double 타입의 공유 변수 var와 그 에 대한 lock을 넘기기 위해 구조체는 두 멤버 변수를 가져야만 한다: volatile 속성의 사용 위치에 주목하라. 이는 포인터 자체가 아니라 var가 volatile임을 나타낸다. 2.6 병렬 코드의 예 쓰래들를 이용하여 쉽게 병렬화를 할 수 있는 프로그램의 예는 두 벡터의 스칼라곱을 계산이다. 주석을 붙인 코드를 제시한다. /* 컴파일 하려면 gcc -D_REENTRANT -lpthread */
|
이 글에는 트랙백을 보낼 수 없습니다
0