[OS] 쓰레드 API

장선규·2023년 8월 21일
0

[OS] OSTEP Study

목록 보기
17/28
post-thumbnail

쓰레드 API

쓰레드 API의 주요 부분을 간략하게 알아보자.

핵심 질문: 쓰레드를 생성하고 제어하는 방법

  • 운영체제가 쓰레드를 생성하고 제어하는데 어떤 인터페이스를 제공해야 할까?
  • 어떻게 이 인터페이스를 설계해야 쉽고 유용하게 사용할 수 있을까?

1. 쓰레드 생성

#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");

2. 쓰레드 종료

다른 쓰레드가 작업을 완료할 때까지 기다리기 위해서는 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은 쓰레드가 리턴할 때 자동적으로 해제되기 때문이다.
  • 프로시저 호출(procedure call): 쓰레드를 생성해 놓고 끝날 때 까지 기다리는 것
    (pthread_create + pthread_join)
  • 모든 멀티 쓰레드 코드가 join 루틴을 쓰진 않는다
    (웹서버의 경우엔 join을 사용할 필요 없음)

3. 락

락(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);
  • trylock: 락 획득 시도하기, 락이 이미 사용중이라면 실패코드 반환
  • timedlock: 락 획득 시고하기, 타임아웃 지나면 실패코드 반환

4. 컨디션 변수

컨디션 변수(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 사용하는 것이 간단하고 안전하다.

profile
코딩연습

0개의 댓글