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달러 씩 입금하려는 상황
예시2)
- producer thread: count++
register1 = count
register1 = register1 + 1
count = register1
- consumer thread: count—
register2 = count
register2 = register2 - 1
count = register2
- 초기 count는 5이다. ( 기댓 값은 6이다 )
- producer: register1 = count {register1 = 5}
- producer: register1 = register1 + 1 {register1 = 6}
- consumer: register2 = count {register2 = 5}
- consumer: register2 = register2 - 1 {register2 = 4}
- producer: count = register1 {count = 6}
- 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, ¶m)){ 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, ¶m)) 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을 요청하지만 이 사항은 중간 우선순위의 쓰레드가 가지고 있음.