Thread Synchronization - 시스템프로그래밍

Thread Synchronization - 시스템프로그래밍

Tag
Computer Science Engineering
System Programming

Mutex

  • “mutual exclusion”의 약자이다.
  • mutex variables는 쓰레드 동기화 메커니즘의 수단으로 사용될 수 있으며 공유 데이터를 보호할 수 있다.
  • “lock”의 개념으로 작동한다.

기본 개념

  • 오직 하나의 쓰레드만 mutex variable을 소유 (lock)할 수 있다.
  • mutex를 사용하면 여러 쓰레드가 mutex에 lock을 하려 하면, 하나의 쓰레드만 성공할 수 있다.
  • lock을 하고 있는 쓰레드는 mutex를 unlock 해야한다.
    • 그래야 다음 waiting queue에 들어가있던 쓰레드가 lock을 할 수 있기 때문이다.
  • 보호되는 데이터를 접근하는 순서를 지켜야한다. ⇒ waiting하다가 다시 lock을 할 수 있어야한다.
⇒ “race” condition을 막아야 한다.
 
예시1)
쓰레드 1과 쓰레드 2가 모두 200달러 씩 입금하려는 상황
notion image
 
예시2)
  • producer thread: count++
register1 = count
register1 = register1 + 1
count = register1
 
  • consumer thread: count—
register2 = count
register2 = register2 - 1
count = register2
 
  • 초기 count는 5이다. ( 기댓 값은 6이다 )
  1. producer: register1 = count {register1 = 5}
  1. producer: register1 = register1 + 1 {register1 = 6}
  1. consumer: register2 = count {register2 = 5}
  1. consumer: register2 = register2 - 1 {register2 = 4}
  1. producer: count = register1 {count = 6}
  1. consumer: count = register2 {count = 4}
 

Mutex

  • 전역변수를 여러개의 쓰레드에서 update하는데에 있어서 자주 mutex를 사용한다
  • variable은 “critical section”에 속해져서 업데이트 되어야 한다.
  • 순서
    • mutex variable 객체를 만들어서 초기화해라
    • mutex를 update하는 여러 개의 쓰레드의 함수는 lock을 요청해야한다.
    • 동시에 요청한 쓰레드 중 하나의 쓰레드만 mutex lock을 허용시킨다.
    • lock을 가진 쓰레드는 critical section 안에서 task를 수행한다.
    • update 작업이 끝나면 mutex unlock
    • 또 다른 쓰레드가 mutex lock을 얻어서 task를 수행한다.
    • mutex 변수를 다 사용했으면 destroy 해야한다.
    •  
  • mutex를 잘 사용하는 것은 프로그래머의 책임이다.
 

Creation/initialization

#include <pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
  • pthread_mutex_t
    • mutex를 사용하려면, lock을 요청하기 이전에 mutex type의 변수를 초기화 해야한다.
    • mutex lock을 표현할 수 있는 변수. 사용하기 이전에 초기화를 꼭 해야한다.
  • static variable의 초기화 하는 방식
    • default variable을 초기화 하는 방법: “PTHREAD_MUTEX_INITIALIZER”
    • dynamic allocated variable을 초기화 하는 방법: pthread_mutex_init 함수를 호출한다.
      • mutex: 초기화하려는 mutex 변수의 포인터
      • attr: mutex 변수 또한 속성을 가지기 때문에 원하는 속성으로 초기화를 하려면 attr를 넣으면 된다.
        • NULL을 넣으면 default value
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero error code
  • 이미 초기화 된 mutex 변수를 다시 초기화 하는 경우: 동작에 대한 결과는 defined 되어있지 않다.
 

Destroy

#include <pthread.h> int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • mutex 변수를 다 사용했다면, destroy를 해야한다. ( 리소스를 효율적으로 사용하기 위해 )
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
  • Undefined behaviors
    • 다 사용했다고 생각하고 destroy를 먼저 시킴. 그 이후 어떤 thread가 mutex를 reference하는 경우
    • 어떠한 쓰레드가 destroy 함수를 호출했고, 다른 쓰레드가 이미 mutex lock을 가지고 있을 경우
      • 즉, 사용하고 있는데 destroy를 하는 경우
 

Locking/unlocking

#include <pthread.h> int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_trylock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • pthread_mutex_lock()
    • mutex를 사용할 수 있을 때까지 호출한 쓰레드를 block 시킨다.
    • 즉, 리턴하는 상황은 해당 쓰레드가 lock을 얻었을 경우
  • pthread_mutex_trylock()
    • lock을 얻을 수 있는지 확인하는 nonblocking 함수.
      • lock을 얻을 수 있다면 바로 lock / 얻을 수 없다면 그냥 return
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
  • Usage example
pthread_mutex_t mylock = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_lock(&mylock); /* critical section */ pthread_mutex_unlock(&mylock);
 

At-Most-Once execution

#include <pthread.h> int pthread_once(pthread_once_t *once_control, void (*init_routine)(void)); pthread_once_t once_control = PTHREAD_ONCE_INIT;
  • at-most-once execution (semantic) 은 기껏 해야 한번 실행하는 함수라는 뜻이다.
    • ex) pthread_mutex_init과 같이 한번만 실행해야 하는 함수
  • pthread_once
    • once_control: pthread_once_t 타입의 변수를 초기화 하고, 해당 변수를 대입
    • init_routine: 처음 실행되었을 때 최초에 한번 만 실행되어야 하는 함수
    • once_control은 무조건 PTHREAD_ONCE_INIT 으로 static 하게 초기화 해야 한다.
 
  • pthread_once() example
#include <pthread.h> #include <stdio.h> static pthread_once_t initonce = PTHREAD_ONCE_INIT; int var; static void initialization(void) { var = 1; printf("The variable was initialized to %d\n", var); } int printinitonce(void) { /* call initialization at most once */ return pthread_once(&initonce, initialization); }
  • printinitonce 함수로 인해 한번만 값이 변경되기 때문에, var은 따로 mutex로 관리할 필요가 없다.
  • 하지만 var에 대입할 값을 파라미터로 넣을 수 없기 때문에, 특정한 값을 넣을 수 없다.
 
  • Alternative example
#include <pthread.h> #include <stdio.h> int printinitmutex(int *var, int value) { // 최초에 한번만 실행이 되고, 다음엔 해당 부분을 넘어가기 때문에 이후에 다시 0 / INITIALIZER 값으로 초기화 될 일은 없다. static int done = 0; static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int error; if (error = pthread_mutex_lock(&lock)) return error; if (!done) { *var = value; printf("The variable was initialized to %d\n", value); done = 1; } return pthread_mutex_unlock(&lock); }
  • pthread_once 함수가 아닌 mutex를 통해 구현
 

At-Least-Once execution

  • 적어도 한번 실행되어야 한다.
  • 초기화를 위해선 at-least-once semantics는 중요하다.
    • 하지만 pthread_mutex_init을 사용하는 경우, at-most-once / at-least-once semantics를 모두 만족해야 한다. ( 즉 한번만 )
 

Condition Variables

Motivation

  • 한 쓰레드가 critical section내의 조건을 만족하지 못해서 태스크를 완료하지 못함.
    • ex) x == y 일 때까지 기다려야한다.
    • Busy waiting solution
      • while(x ≠ y);
      • 비효율적
    • Non-busy waiting solution
      • Lock a mutex
      • x == y 인지 조건을 따진다.
      • true 이면, mutex unlock 하고 loop을 나간다.
      • false 이면, thread를 suspend 하며 mutex unlock
        • 즉, 내가 못하니까 다른 쓰레드에게 기회를 주기 위해 waiting queue에 들어가게 된다.
      • 또 다른 쓰레드가 x / y의 값을 변경하게 되면, condition을 체크하기 위해 대기중인 쓰레드를 깨운다.
 

Overview

  • Condition variables는 또다른 종류의 쓰레드들 간의 동기화가 되어 실행될 수 있는 메커니즘
    • mutex: 공유되는 데이터 접근을 막기 위한 동기화 메커니즘
    • condition variables: 실제 공유 데이터의 값을 기반 동기화 메커니즘
  • critical section 내에서 이루어져야 한다. 즉 mutex lock을 선행해야 한다.
  • condition variables가 없다면
    • 개발자는 쓰레드가 특정 조건이 만족될 때까지 쓰레드에서 계속 polling하는 작업을 해야한다.
    • 이것은 쓸데 없이 리소스를 낭비하는 일이다.
 
  • condition variable
    • 쓰레드간에 동기화를 시킬 수 있는 새로운 데이터 타입. mutex와 마찬가지로 내부적으로 waiting queue가 존재한다.
  • pthread_cond_wait
    • condition variable과 mutex를 파라미터로 받으며, atomic하게 호출한 쓰레드를 suspend시키고 mutex unlock
    • mutex를 다시 얻고 return 된다.
    • mutex lock을 얻은 상황에서 호출되어야 한다.
  • pthread_cond_signal
    • condition variable을 파라미터로 받는다. 적어도 하나의 쓰레드를 깨운다
    • condition variable에서 대기중인 쓰레드를 mutex의 waiting queue로 옮기는 역할을 한다.
 

Condition variable example

  • x==y 인 조건을 기다린다.
  • v: condition variable / m: mutex
pthread_mutex_lock(&m); while( x != y ) pthread_cond_wait(&v, &m); /* modify x or y if necessary */ pthread_mutex_unlock(&m);
 
  • 또다른 쓰레드
pthread_mutex_lock(&m); x++; pthread_cond_signal(&v); pthread_mutex_unlock(&m);
 

Creating condition variables

#include <pthread.h> int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
mutex 사용 방식과 동일하다
  • pthread_cond_t
    • static하게 PTHREAD_COND_INITIALIZER로 초기화
    • dynamic하게 pthread_cond_init을 호출
      • attr를 통해 속성을 넣을 수 있다.
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
  • 중복해서 초기화하면 안된다.
 

Destroying condition variables

#include <pthread.h> int pthread_cond_destroy(pthread_cond_t *cond);
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
  • Undefined behaviors
    • destroy한 condition variable을 참조하면 안된다.
    • 다른 쓰레드가 condition variable을 참조하고 있는데, 또 다른 쓰레드에서 해당 condition variable을 destroy 하면 안된다.
 

Waiting on condition variables

#include <pthread.h> int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
  • pthread_cond_wait: 다른 쓰레드가 condition variable에 대해서 signal을 호출하면 깨어남
  • pthread_cond_timedwait: 누가 깨워주지 않아도 시간이 만료되면 깨어남
    • cond: pthread_cond_t 변수
    • mutex: unblock시킬 mutex 변수
    • abstime: 얼마나 시간이 지나면 깨어날 지
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
    • 타이머가 만료되어 리턴되는 경우 ETIMEDOUT이 반환됨
  • wait함수를 호출하여 쓰레드가 블락된 상황에서 Signal이 도착했을 경우
    • signal handler가 불려서 return이 되고나서 기다리는 상태가 계속 될 수 있음
    • 잘못된 경우로 쓰레드가 깨어나는 상황이 생길 수 있음
  • mutex 변수는 쓰레드가 lock을 가지고 있어야 하는데, 다른 mutex lock을 지정했을 경우 오동작을 하거나 deadlock에 빠질 수 있다.
 

Signaling on condition variables

#include <pthread.h> int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);
  • pthread_cond_signal
    • condition variable의 waiting queue에 들어가있는 스레드 중 적어도 하나의 쓰레드를 깨운다. 즉 waiting queue에서 꺼내서 mutex waiting queue로 옮긴다.
  • pthread_cond_braodcast
    • waiting queue에 있는 모든 쓰레드를 unblock한다.
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
 

Signal handling and threads

Signal delivery in threads

  • 프로세스안에 있는 모든 쓰레드는 프로세스의 시그널 핸들러를 공유한다.
  • 각각의 쓰레드는 개인의 signal mask를 가진다.
  • 시그널이 전달되는 세가지 방식
    • Asynchronous
      • 그 시그널을 막지 않은 쓰레드에게 시그널을 전달하는 경우 ( 시그널 마스크로 막히지 않은 )
    • Synchronous
      • 시그널을 발생시킨 쓰레드에게 시그널을 전달하는 경우
    • Directed
      • 타겟 쓰레드를 지정한 경우 ( pthread_kill )

Directing a signal

#include <signal.h> #include <pthread.h> int pthread_kill(pthread_t thread, int sig);
  • sig 시그널을 thread 쓰레드에게 전달한다.
  • 성공했을 시 0 / 실패했을 시 nonzero
 

Masking signals for threads

#include <pthread.h> #include <signal.h> int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
  • how
    • SIG_SETMASK
      • 기존 시그널 마스크를 무시하고, set으로 덮어 쓴다.
    • SIG_BLOCK
      • 현재 시그널 마스크 값에 set을 추가한다.
    • SIG_UNBLOCK
      • 현재 시그널 마스크 값에서 set에 해당하는 값을 제거한다.
  • oset
    • NULL이 아니라면 변경되기 이전의 시그널 마스크 값을 저장하기 위한 output parameter을 받을 수 있음
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
    •  

Dedicating threads for signal handling

  • 시그널 핸들링을 전담하는 쓰레드를 지정하는 방식을 사용하겠다.
  • 다중 쓰레드가 존재하는 프로세스에서 signal handling을 해야할 필요가 있을 때 아래와 같은 방식을 사용하자
    • 시그널 핸들링을 처리하는 특정한 쓰레드를 지정해라.( 다른 쓰레드에게는 시그널이 전달되지 않도록 )
      • main thread는 모든 시그널을 막는다.
      • 시그널 전담 쓰레드를 생성한다.
      • 전담 쓰레드는 sigwait 함수를 호출한다.
      • 또 다른 방법으로는, 쓰레드가 pthread_sigmask를 통해서 타겟 시그널을 unblock하는 방법이 있다.

Example

#include <errno.h> #include <pthread.h> #include <signal.h> #include <stdio.h> #include "doneflag.h" #include "globalerror.h" static int signalnum = 0; /* ARGUSED */ static void *signalthread(void *arg){ int error; sigset_t intmask; struct sched_param param; int policy; int sig; if(error = pthread_getschedparam(pthread_self(), &policy, &param)){ seterror(error); return NULL; } fprintf(stderr, "Signal thread entered with policy %d and priority %d\n", policy, param.sched_priority); // signalnum을 intmask에 설정하고 sigwait함수를 호출하며 suspend가 된다. if((sigemptyset(&intmask) == -1 || (sigaddset(&intmask, signalnum) == -1) || (sigwait(&intmask, &sig) == -1)) seterror(errno); // setdone()를 seterror하여 워커쓰레드들에게 하던 작업을 끝내고 프로그램을 마치라는 것을 알리는 용도 else seterror(setdone()); return NULL; } // 시그널 처리 전담 쓰레드 생성 int signalthreadinit(int signo){ int error; pthread_attr_t highprio; struct sched_param param; int policy; sigset_t set; pthread_t sighandid; // signalnum은 전역변수로서 저장하며 타겟 시그널을 저장한느 용도 signalnum = signo; // 프로세스 단계에 signalnum을 블락 시킨다. if((sigemptyset(&set) == -1) || (sigaddset(&set, signalnum) == -1) || (sigprocmask(SIG_BLOCK, &set, NULL) == -1)) return errno; // 쓰레드의 attr를 설정한다. if(param.sched_priority < sched_get_priority_max(policy)){ param.sched_priority++; if(error = pthread_attr_setschedparam(&highprio, &param)) return error; }else fprintf(stderr, "Warning, cannot increase priority of signal thread.\n"); // 전담 쓰레드를 생성하며, 전담 쓰레드가 실행할 함수 signalthread를 argument로 넘긴다. if(error = pthread_create(&sighanid, &highprio, signalthread, NULL)) return error; return 0; }
  • 메인 쓰레드에서 signalthreadinit함수를 호출함으로써 ‘signo’ 시그널을 block 시키고, 시그널 전담 쓰레드를 만들어서 시그널을 기다리도록 함.
  • ‘signo’ 시그널이 와서 pending이 되면, sigwait 함수가 리턴이 되면서 전담 쓰레드는 setdone() 함수를 호출한다.
  • sigwait 함수 자체가 pending list에 있던 시그널이 삭제되기 때문에, 별도의 signal handler 함수가 사용되지 않고 있다.

Readers and writers

Reader-write problem

  • 쓰레드가 리소스를 접근한다. 라는 것은 두가지 타입 중 중 하나에 해당한다. ( reading / writing )
    • write: 여러개의 쓰레드가 접근해서 쓰면 안된다. exclusively 하게.
    • read: 공유될 수 있다.
  • Two common strategies ( reader / write 쓰레드가 모두 access 하려 할 때 )
    • Strong reader synchronization
      • writer 쓰레드가 resource access하고 있지 않는 한에는 reader 쓰레드에게 우선권을 주겠다.
    • Strong writer synchronization
      • write 쓰레드에게 우선권을 주겠다.
  • POSIX 에서는 read-write lock을 제공한다.
    • read operation용으로 lock을 요청? / write operation용으로 lock을 요청?
    • 여러개의 쓰레드에게 lock을 줄 수도 있는 메커니즘
    • ⇒ write operation을 하겠다는 쓰레드가 접근하게 된다면 다른 쓰레드는 접근할 수 없도록 한다.
 

Initialization of read-write locks

#include <pthread.h> int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
  • pthread_rwlock_t
    • read-write lock을 나타내는 변수 타입
    • 항상 초기화를 하고 나서 사용을 해라.
  • attr
    • pointer of read-write lock attribute object
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
  • 초기화가 이미 되었는데 초기화를 하려 한다면
    • 그 내용은 명시되어있지 않다.
 

Destroying read-write locks

#include <pthread.h> int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
  • destroy된 lock 변수를 다시 사용하려면 pthread_rwlock_init함수를 통해 reinitialize 할 수 있다.
  • read-write lock 변수가 이미 destroy가 되었는데, 다시 참조를 하려 한다면
    • 그 내용은 명시되어있지 않다.
 

Locking / unlocking

#include <pthread.h> int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
read-write lock을 요청하며, lock을 얻지 못하면 read-write waiting queue에 대기한다.
  • rdlock / tryrdlock
    • read 목적으로 lock을 요청한다.
  • wrlock / trywrlock
    • write 목적으로 lock을 요청한다.
  • unlock
    • lock을 해제한다.
  • return values
    • 성공했을 시 0 / 실패했을 시 nonzero
    • nonblocking 모드로 요청한 tryrdlock / trywrlock의 경우 lock을 얻지 못하면 EBUSY로 리턴된다.
  • 다른 쓰레드가 wrlock을 이미 얻었는데, 쓰레드가 rdlock을 요청하게 된다면 내가 나를 기다리는, 즉 deadlock 상황이 생긴다.
    • deadlock이 발생하지 않도록 잘 사용해야 한다.
 

Example

리스트를 사용하며 리스트를 접근하는 코드
 
listlibrw_r.c
#include <errno.h> #include <pthread.h> static pthread_rwlock_t listlock; static int lockiniterror = 0; static pthread_once_t lockisinitialized = PTHREAD_ONCE_INIT; static void ilock(void){ lockiniterror = pthread_rwlock_init(&listlock, NULL); } int initialize_r(void){ // 한번만 rwlock을 init하도록 if(pthread_once(&lockisinitialized, ilock)) lockiniterror = EINVAL; return lockiniterror; } int accessdata_r(void){ int error; int errorkey = 0; int key; // 하나의 쓰레드만 아래를 접근할 수 있도록 write lock if(error = pthread_rwlock_wrlock(&listlock)){ errno = error; return -1; } // accessdata는 쓰레드간 값이 변경될 수도 있는 함수 key = accessdata(); if(key == -1){ errorkey = errno; pthread_rwlock_unlock(&listlock); errno = errorkey; return -1; } if(error = pthread_rwlock_unlock(&listlock)){ errno = error; return -1; } return key; } int adddata_r(data_t data){ int error; if(error = pthread_rwlock_wrlock(&listlock)){ errno = error; return -1; } // write operation을 수행하는 task if(adddata(key) == -1){ error = errno; pthread_rwlock_unlock(&listlock); errno = error; return -1; } if(error = pthread_rwlock_unlock(&listlock)){ errno = error; return -1; } return 0; } int getdata_r(int key, data_t *datap){ int error; if(error = pthread_rwlock_rdlock(&listlock)){ errno = error; return -1; } // read operation을 수행하는 task // 만약 getdata_r 함수만 호출한다면 모든 쓰레드가 접근할 수 있다. if(getdata(key) == -1){ error = errno; pthread_rwlock_unlock(&listlock); errno = error; return -1; } if(error = pthread_rwlock_unlock(&listlock)){ errno = error; return -1; } return 0; } int freekey_r(int key){ int error; if(error = pthread_rwlock_wrlock(&listlock)){ errno = error; return -1; } // write operation을 수행하는 task if(freekey(key) == -1){ error = errno; pthread_rwlock_unlock(&listlock); errno = error; return -1; } if(error = pthread_rwlock_unlock(&listlock)){ errno = error; return -1; } return 0; }
 
  • initialize_r()
    • 초기화 하는 함수이며, pthread_once 함수를 통해 한번만 초기화하도록
 
  • mutex lock과의 차이점
    • read-write lock은 overhead가 있을 수 있다.
    • 하지만 read operation만 요청하는 경우 advantage가 있다.
 

A strerror_r implementation

strerror()

  • 문제점
    • thread-safe하지 않은 함수이다.
    • 만약 concurrent하게 사용한다면 문제가 있기 때문에, mutex lock을 이용해 protect할 수 있다.
    • perror 또한 마찬가지이다. + async-signal safe하지도 않다.
 
#include <errno.h> #include <pthread.h> #include <signal.h> #include <stdio.h> #include <string.h> // mutex lock static하게 초기화 static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; int strerror_r(int errnum, char *strerrbuf, size_t buflen){ char *buf; int error1; int error2; int error3; sigset_t maskblock; sigset_t maskold; if((sigfillset(&maskblock) == -1) || (sigprocmask(SIG_SETMASK, &maskblock, &maskold) == -1)) return errno; if(error1 = pthread_mutex_lock(&lock)){ (void)sigprocmask(SIG_SETMASK, &maskold, NULL); return error1; } // 기존 strerror 함수를 쓰레드 하나만 호출할 수 있도록 critical section 내에서 호출 // async-signal 내에서도 safe하게 사용할 수 있도록 sigprocmask 함수 호출 뒤 사용 buf = strerror(errnum); if(strlen(buf) >= buflen) error1 = ERANGE; else (void *)strcpy(strerrbuf, buf); error2 = pthread_mutex_unlock(&lock); error3 = sigprocmask(SIG_SETMASK, &maskold, NULL); return error1 ? err1 : (error2 ? error2 : error3); } int perror_r(const chasr *s){ int error1; int error2; sigset_t maskblock; sigset_t maskold; if((sigfillset(&maskblock) == -1) || (sigprocmask(SIG_SETMASK, &maskblock, &maskold) == -1)) return errno; if(error1 = pthread_mutex_lock(&lock)){ (void)sigprocmask(SIG_SETMASK, &maskold, NULL); return error1; } perror(s); error1 = pthread_mutex_unlock(&lock); error2 = sigprocmask(SIG_SETMASK, &maskold, NULL); return error1? error1 : error2; }
 

Deadlocks

Deadlocks and other pesky problems

  • 쓰레드가 pthread_mutex_lock 을 요청했는데, 그 전에 이미 그 쓰레드가 lock을 가지고 있었던 경우
    • 스스로 deadlock에 걸리게 됨.
    • 하지만 pthread_mutex_lock은 내부적으로 해당 상황에서는 EDEADLK 이라는 에러코드를 반환받을 수도 있다.
  • lock을 가진 쓰레드가 에러를 만나는 경우 바로 리턴하는 경우?
    • 다른 쓰레드들이 계속 lock을 기다릴 수 있기 때문에 주의해라.
  • Thread 간의 priority inversion 문제 ( 우선순위 역전 문제 / starvation )
    • 세 종류의 priority 쓰레드가 있었음. ( 낮음 / 중간 / 높음 )
    • 중간 우선순위의 쓰레드가 실행되면 오래 실행이 된다.
    • 낮은 우선순위의 쓰레드가 lock을 얻고 작업을 하다가, 중간 우선순위의 쓰레드가 등장하였음. 그때는 낮은 우선순위의 쓰레드가 pending / 중간 우선순위의 쓰레드가 오래 작업을 하게 됨. 이때 높은 우선순위의 쓰레드가 등장하게 되면 중간 우선순위의 쓰레드가 pending / 높은 우선순위의 쓰레드 실행. 높은 우선순위의 쓰레드가 낮은 우선순위가 이미 가지고있는 변수에 대해 lock을 요청하지만 이 사항은 중간 우선순위의 쓰레드가 가지고 있음.
    •