쓰레드 API의 주요 부분을 간략하게 알아보자.
핵심 질문: 쓰레드를 생성하고 제어하는 방법
- 운영체제가 쓰레드를 생성하고 제어하는데 어떤 인터페이스를 제공해야 할까?
- 어떻게 이 인터페이스를 설계해야 쉽고 유용하게 사용할 수 있을까?
#include <pthread.h>
int pthread_create(thread, attr, function, arg);
thread
: pthread_t
타입 구조체를 가리키는 포인터attr
: 쓰레드의 속성 지정function
: 실행시킬 함수 (함수포인터)arg
: 실행할 함수(function
)에 들어갈 인자int rc = pthread_create(&p1, NULL, mythread, "A");
다른 쓰레드가 작업을 완료할 때까지 기다리기 위해서는 prhtead_join()
을 호출한다.
int pthread_join(thread, value_ptr);
thread
: pthread_t
타입 구조체를 가리키는 포인터value_ptr
: 반환 값에 대한 포인터void *mythread(void *arg){
myarg_t *m = (myarg_t *) arg;
printf(“%d %d\n”, m−>a, m−>b);
myret_t r; // 스택에 할당하면 안 된다!!!
r.x = 1;
r.y = 2;
return (void *) &r;
}
r
은 쓰레드가 리턴할 때 자동적으로 해제되기 때문이다.락(lock)을 통해 임계 영역에 대한 상호 배제를 할 수 있다.
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
사용 방법은 다음과 같다
pthread_mutex_t lock;
int rc = pthread_mutex_init(&lock, NULL); // 락 동적 초기화
assert(rc == 0); // 성공 여부 확인
pthread_mutex_lock(&lock);
x = x + 1; // 임계 영역 코드를 락으로 보호
pthread_mutex_unlock(&lock);
lock = PTHREAD_MUTEX_INITIALIZER;
락 획득 함수
pthread_mutex_trylock(mutex);
pthread_mutex_timedlock(mutex, abs_timeout);
컨디션 변수(condition variable): 어떤 실행의 상태가 원하는 것과 다를 때, 조건이 참이 되기를 기다리며 쓰레드가 대기할 수 있는 큐이다.
int pthread_cond_wait(pthread_cond_t* cond, pthread_mutex_t* mutex);
int pthread_cond_signal(pthread_cond_t* cond);
한 쓰레드가 계속 진행하기 전에 다른 쓰레드가 무언가를 해야하는 경우, 쓰레드 간에 시그널 교환 메커니즘이 필요하다.
컨디션 변수 사용을 위해서는 "반드시" 컨디션 변수와 연결된 락이 존재해야 한다.
사용 용례)
// 쓰레드 1: 다른 쓰레드가 wake 시키기 전까지 sleep
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
Pthread_mutex_lock(&lock);
while (ready == 0) // ready는 전역 변수
Pthread_cond_wait(&cond, &lock); // sleep 상태가 됨, 락 반납 (다시 깨면 락 다시 획득)
Pthread_mutex_unlock(&lock);
// 쓰레드 2: 쓰레드 1을 wake 시킴
Pthread_mutex_lock(&lock);
ready = 1; // ready는 전역 변수
Pthread_cond_signal(&cond); // wake 시그널 보내기
Pthread_mutex_unlock(&lock);
시그널을 보내고 전역변수 ready를 수정할 때는 반드시 락을 가지고 있어야 경쟁 조건이 발생하지 않는 것을 보장한다.
시그널 wait 함수는 호출 시 내부적으로 pthread_unlock_mutex()가 호출되어 락을 반납하고, 해당 쓰레드가 sleep 상태가 된다.
(락을 반납하지 않으면 다른 쓰레드가 락을 획득하지 못함)
이후 누군가가 다시 깨우면, 락을 다시 획득한다.
대기 쓰레드 조건 검사 시 if 대신 while 사용하는 것이 간단하고 안전하다.