Search

<pthread.h>

Created
2021/07/20
tag
C
Philosophers
pthread.h
thread
attr
mutex

Subjects

<pthread.h>에는 pthread를 다룰 수 있는 함수들이 다수 존재한다. pthread의 생성 및 소멸, Data Race 방지를 위한 mutex 등의 조작을 할 수 있다. 쓰레드에 대한 별도의 설정 없이는 User-Level 쓰레드를 이용하게 되지만, 쓰레드의 속성 설정을 통해 Kernel-Level 쓰레드로 이용하는 것도 가능하다. 수 많은 함수와 정의된 매크로 값들 중에서도 pthread_create, pthread_join, pthread_detach와 같은 쓰레드의 생성 및 소멸 함수, pthread_mutex_init, pthread_mutex_destroy와 같은 mutex의 생성 및 소멸 함수, pthread_mutex_lock, pthread_mutex_unlock과 같은 mutex의 잠금 함수, 그리고 쓰레드의 속성 관련 함수들에 대해서만 간단히 알아볼 것이다.

1. 쓰레드 속성

쓰레드 속성 값은 아래에 주어진 함수들을 통해 초기화하고 설정할 수 있는데, 매뉴얼을 살펴보면 더 많은 설정 값을 제어할 수 있으므로 필요한 경우에는 반드시 매뉴얼을 살펴보도록 하자.

1) pthread_attr_init

함수 원형

int pthread_attr_init(pthread_attr_t *);
C
복사

함수 인자

쓰레드의 속성을 결정지을 pthread_attr_t의 주소를 받는다. 함수 내부에서는 인자로 받은 주소에 pthread_attr_t를 할당한다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

pthread_attr_init으로 이미 초기화 된 pthread_attr_t를 다시 인자로 넣어 호출하는 행위는 Undefined Behavior이다. 또한 pthread_attr_init을 통해 초기화된 pthread_attr_t는 이용을 마쳤을 때 반드시 pthread_attr_destory의 호출이 요구된다.
pthread_attr_init의 호출로 pthread_attr_t에 할당된 값은 속성의 기본 값이다. 따라서 속성을 이용하고 싶다면 별도로 값에 대한 조정이 필요하다. 이를 지원하는 함수들은 속성에 따라 마련이 되어 있으니 할당받은 pthread_attr_t의 주소를 인자로 넘겨 주기만 하면 된다. 해당 함수들은 아래에서 다룬다.
pthread_attr_destory의 호출이 되었다가 pthread_attr_init을 호출하는 것은 아무런 문제가 없다.

2) pthread_attr_destory

함수 원형

int pthread_attr_destroy(pthread_attr_t *);
C
복사

함수 인자

기존에 pthread_attr_init으로 초기화 된 pthread_attr_t의 주소를 인자로 받는다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

초기화 되지 않은 pthread_attr_tpthread_attr_destroy에 이용하는 것은 Undefined Behavior이다.

3) pthread_attr_setdetachstate

함수 원형

int pthread_attr_setdetachstate(pthread_attr_t *, int);
C
복사

함수 인자

pthread_attr_init을 통해 초기화 된 pthread_attr_t의 주소를 인자로 준다. 생성할 쓰레드가 어떤 상태를 가질지를 int 타입의 값으로 명시한다.
사용할 수 있는 상태 값은 PTRHEAD_CREATE_JOINABLE, PTHREAD_CREATE_DETACHED이다. 만일 pthread_attr_setdetachstate 호출 없이 pthread_attr_t를 이용하면 이 때의 기본 설정은 PTHREAD_CREATE_JOINABLE이다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

User-LevelKernel-Level 쓰레드와 같은 쓰레드의 사용처에 따른 분류 외에도 쓰레드가 쓰레드 내에서 사용한 자원에 대한 관점에 따라서도 분류할 수 있다. 기본적으로 쓰레드는 상태에 따른 Joinable 쓰레드와 Detached 쓰레드로 나뉜다.
Joinable 쓰레드의 경우에는 쓰레드의 종료 시에 반드시 pthread_join 함수를 이용하여 자원을 반환해줘야 하며, 이를 통해 쓰레드가 종료되었음을 다른 쓰레드에 알릴 때 이용된다. Joinable 쓰레드의 경우에는 wait 혹은 waitpid 함수의 맥락과 비슷하다.
반대로 Detached 쓰레드는 쓰레드의 종료 시에 스스로 자원을 반환해주어 별도의 pthread_join이 요구되지 않는다. 따라서 다른 쓰레드와 독립적으로 동작하는 경우에 이용된다.

4) pthread_attr_getdetachstate

함수 원형

int pthread_attr_getdetachstate(const pthread_attr_t *, int *);
C
복사

함수 인자

확인하고자 하는 pthread_attr_t의 주소를 받는다. pthread_attr_t에 기록된 쓰레드의 상태 설정 값을 확인할 수 있도록 int 타입의 주소를 받아 역참조하여 설정 값을 저장한다.
기록되는 값은 상태 매크로 값인 PTHREAD_CREATE_JOINABLE 혹은 PTHREAD_CREATE_DETACHED이다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

5) pthread_attr_setscope

함수 원형

int pthread_attr_setscope(pthread_attr_t *, int);
C
복사

함수 인자

pthread_attr_init을 통해 초기화 된 pthread_attr_t의 주소를 인자로 준다. 생성할 쓰레드가 어떤 범위를 가질지를 int 타입의 값으로 명시한다.
사용할 수 있는 상태 값은 PTRHEAD_SCOPE_SYSTEM, PTHREAD_SCOPE_PROCESS이다. 만일 pthread_attr_setscope 호출 없이 pthread_attr_t를 이용하면 이 때의 기본 설정은 PTHREAD_SCOPE_SYSTEM이다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

Critical Section에 접근하기 위해 LOCK을 얻으려고 경쟁하는 상태를 Contention이라고 하는데, 이 때 Contention의 범위를 설정할 때 pthread_attr_setscope가 이용된다. 기본적으로 쓰레드의 생성은 User-Level 쓰레드이며, Kernel-Level 쓰레드의 생성은 시스템에 달려 있다. 여기서 범위의 설정이 기여하는 바는 종류에 따른 쓰레드 생성이 아니라, User-Level 쓰레드가 Kernel-Level 쓰레드에 어떻게 Mapping 될지에 따른 Contention의 설정이다.
PTHREAD_SCOPE_SYSTEM으로 설정된 쓰레드들은 시스템 상에서 Global하게 Contention을 갖기 때문에, 각 쓰레드들은 하나의 Kernel-Level 쓰레드로 Mapping 되는 것으로 이해된다. 주로 One-to-One에서 PTHREAD_SCOPE_SYSTEM으로 설정하여 이용한다.
PTHREAD_SCOPE_PROCESS로 설정된 쓰레드들은 프로세스 상에서 Local하게 Contention을 갖기 때문에, 여러 쓰레드들이 하나의 Kernel-Level 쓰레드를 공유하는 것으로 이해된다. 주로 Many-to-One에서 PTHREAD_SCOPE_PROCESS로 설정하여 이용한다.
Many-to-Many에서는 PTHREAD_SCOPE_PROCESS 혹은 PTHREAD_SCOPE_SYSTEM 어떤 것을 사용해도 무방하다.
위 그림에서 제시된 Light Weight Process (LWP)User-Level 쓰레드를 Kernel-Level 쓰레드에 Mapping 하기 위한 일종의 인터페이스이고, Process Contention ScopeSystem Contention Scope를 의미하는 말이 각각 PCSSCS이다.
사용하고 있는 쓰레드 라이브러리에서 TiT_{i}로 표기된 User-Level 쓰레드를 LWP라는 스케줄링 단위로 만들어 내고, 이들이 Kernel-Level 쓰레드의 Mapping 되어 곧 운영체제에 의해 스케줄링 된다. 즉, PCSLWP의 점유를 위한 Contention을 벌이고, SCS는 각각의 쓰레드가 LWP가 됨에 따라 Kernel-Level 쓰레드가 스케줄링 될 때 Contention이 벌어지는 것을 볼 수 있다.

6) pthread_attr_getscope

함수 원형

int pthread_attr_getscope(const pthread_attr_t *restrict, int *restrict);
C
복사

함수 인자

확인하고자 하는 pthread_attr_t의 주소를 받는다. pthread_attr_t에 기록된 쓰레드의 범위 설정 값을 확인할 수 있도록 int 타입의 주소를 받아 역참조하여 설정 값을 저장한다.
기록되는 값은 범위 매크로 값인 PTHREAD_SCOPE_SYSTEM 혹은 PTHREAD_SCOPE_PROCESS이다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

7) pthread_attr_setstacksize

함수 원형

int pthread_attr_setstacksize(pthread_attr_t *, size_t);
C
복사

함수 인자

pthread_attr_init을 통해 초기화 된 pthread_attr_t의 주소를 인자로 준다. 생성할 쓰레드가 몇 바이트의 Stack을 이용할지, 그 크기를 size_t 타입의 값으로 명시한다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

만일 인자로 넣은 값이 <limits.h>에 정의된 PTHREAD_STACK_MIN 보다 작은 값으로 설정되면 errnoEINVAL이 된다. 몇 시스템에서는 페이지 단위 값의 배수가 아니라면 errnoEINVAL로 만든다. 이와 같은 시스템을 제외한 나머지 시스템에서는 인자로 넣은 값이 페이지 단위 값에 맞지 않다면 할당할 수 있는 값 중 인자 값보다 큰 최소 값을 할당한다.
Mac OS X에서는 반드시 페이지 단위 값의 배수로 Stack 크기를 정해야 한다.

8) pthread_attr_getstacksize

함수 원형

int pthread_attr_getstacksize(const pthread_attr_t *restrict, size_t *restrict);
C
복사

함수 인자

확인하고자 하는 pthread_attr_t의 주소를 받는다. pthread_attr_t에 기록된 쓰레드의 Stack 크기 값을 확인할 수 있도록 size_t 타입의 주소를 받아 역참조하여 설정 값을 저장한다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

2. 쓰레드

1) pthread_create

함수 원형

int pthread_create(pthread_t *restrict, const pthread_attr_t *restrict, void *(*)(void*), void *restrict);
C
복사

함수 인자

User-Level 쓰레드가 생성될 pthread_t 타입의 주소 값과 User-Level 쓰레드의 속성 값이 존재하는 pthread_attr_t의 주소 값을 인자로 받는다. 속성 값을 이용하여 문제 없이 쓰레드가 생성되었다면, 쓰레드의 작업은 특정 함수를 실행하면서 수행된다. 따라서 작업을 수행하기 위해서 세 번째 인자로 함수 포인터를 이용하며, 해당 함수에서 사용할 인자를 네 번째 인자 값으로 명시한다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

별도의 속성 값을 사용하지 않는다면 두 번째 인자를 NULL로 주면 되고, 기본 속성으로 쓰레드가 생성된다. 또한 쓰레드 내에서 수행할 작업에 대해서는 Non-Nullable이므로 반드시 기재 되어야 한다. 해당 작업에서 사용할 인자는 Nullable이므로 만일 작업 내에서 별도의 인자를 요구하지 않는다면 pthread_create의 네 번째 인자 역시 NULL을 주면 된다.
생성된 쓰레드에서 사용할 인자는 라이브러리 구현에 따라 가변 인자를 활용하여 여럿 받을 수도 있지만, <pthread.h>에서는 void *단일 인자를 사용하기 때문에 쓰레드 내에서 여러 인자가 필요한 경우 별도의 구조체를 정의하여 인자들을 Wrapping 작업이 요구된다. 또한 쓰레드로 실행할 작업은 함수 포인터로 명시됨에 따라 void *를 반환하고 void *를 인자로 받는 원형으로 선언 및 정의되어야 한다. 함수 포인터의 형 변환을 이용하는 방법도 있겠지만, 권장되지 않는다.
쓰레드에서 실행할 작업의 경우 void * 반환 타입인 것을 볼 수 있는데, PTHREAD_CREATE_JOINABLE로 설정된 쓰레드의 경우에만 void * 반환 값을 이용할 수 있다. 즉, PTHREAD_CRAETE_DETACHED의 경우에는 독립적인 쓰레드로 간주하여 종료 즉시 자원을 반환하도록 되어 있으므로 반환 값을 이용하는 것이 불가능하다.
Joinable 쓰레드의 경우, 반환된 값은 pthread_join을 통해 넘겨 받아서 활용할 수 있다. 쓰레드 내에서 함수 호출이 복잡하여 호출한 함수 내에서 Fallback을 수행해야 하는 경우, 반환을 통해서 pthread_join에 값을 넘기는 방법 외에도 pthread_exit을 통해 pthread_join이 반환 값을 활용하도록 할 수 있다.
제시된 함수들은 아래에서 다룬다.

2) pthread_join

함수 원형

int pthread_join(pthread_t, void **);
C
복사

함수 인자

어떤 쓰레드의 작업이 종료될 때까지 대기할지 구분하기 위해 pthread_t 타입의 ID 값을 첫 번째 인자로 받는다. 이 때 해당 쓰레드의 작업이 종료되면 자원을 모두 회수하면서 쓰레드가 반환한 주소 값을 포인터로 받아 올 수 있는데 이를 이중 포인터로 된 두 번째 인자를 통해 받아온다.
pthread_t는 시스템에 따라 정수 값일 수도 있고 그렇지 않을 수도 있다. Mac OS X에서는 pthread_t가 구조체로 되어 있다. 이 때 pthread_t가 어떤 타입으로 정의되어 있든 간에 pthread_tID 값으로 사용할 수 있는데, 이는 메모리 자체에 쓰이는 값이 동일한 프로세스 내의 각 쓰레드에서는 고유한 값으로 할당되기 때문이다. 물론 엄밀히 따지면 이는 쓰레드를 구분하는 고유한 값일 뿐 gettid라는 함수로 얻을 수 있는 운영체제에서의 쓰레드 ID와는 다르다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

pthread_join은 작업이 완료될 때까지 기다렸다가 자원을 회수한다는 개념에서 waitwaitpid와 비슷하다. <pthread.h>의 쓰레드는 JoinableDetached로 나뉘는데 pthread_joinJoinable 쓰레드의 종료까지 대기하고 이에 대한 자원을 회수할 때 이용된다. 별도로 쓰레드의 속성 값을 지정하지 않는다면 Joinable 쓰레드로 생성되므로 pthread_join의 호출이 필수적으로 요구된다. 이 때 Detached 쓰레드에 대해서는 pthread_join이 정상적으로 작동하지 않기 때문에 반드시 Joinable 쓰레드에 대해서 pthread_join을 이용할 수 있도록 해야 한다.
또한 Joinable 쓰레드는 다른 쓰레드와 구분지어 독립적으로 실행되는 Detached 쓰레드와는 차이가 있기 때문에, pthread_join을 통해 해당 쓰레드가 반환하는 값을 받아 활용하는 것이 가능하다. 만일 Joinable 쓰레드가 pthread_cancel에 의해 중지되면, pthread_join으로 얻을 수 있는 쓰레드의 반환 값은 PTHREAD_CANCELED라는 매크로 값이 된다.
Joinable 쓰레드의 반환 값은 두 번째 인자를 통해 이용할 수 있는데, 반환 값을 받더라도 이용하지 않을 것이라면 두 번째 인자를 NULL로 두고 pthread_join을 호출하면 된다.
두 번째 인자가 이중 포인터인 이유는 쓰레드 실행 시 반환 값이 단일 포인터라는 점을 통해 유추할 수 있다. 단일 포인터의 반환을 다른 블록의 단일 포인터에 할당하기 위해선 주소의 Value로 할당하는 것이 아니라 주소의 Reference를 통한 할당이 요구된다.

3) pthread_detach

함수 원형

int pthread_detach(pthread_t);
C
복사

함수 인자

Detached로 생성된 쓰레드에 대해 독립적인 쓰레드임을 명시하기 위해 pthread_detach를 이용한다. 따라서 해당 쓰레드를 인식할 수 있는 pthread_t 타입의 ID를 인자로 받는다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

단순히 쓰레드의 생성 시 속성 값으로 PTHREAD_CREATE_DETACHED를 이용했다고 해서 독립적인 쓰레드로써 작업을 마친 후 자동으로 자원을 반환해주는 것이 아니다. Detached 쓰레드도 pthread_detach를 통해 독립적인 쓰레드임을 명시하여, 작업을 완료했을 때 자동으로 자원을 반환할 수 있도록 만들어야 한다. Detached 쓰레드에게 사용하는 pthread_detach는 특성 상 쓰레드의 종료를 대기하지 않는다.

4) pthread_cleanup_push

함수 원형

void pthread_cleanup_push(void (*routine)(void *), void *arg);
C
복사

함수 인자

쓰레드의 작업을 수행하는 함수가 코드를 끝까지 수행하기 전에 종료된 경우, 이를 정리하는 함수를 호출할 수 있도록 Handler를 등록할 수 있다. 이에 해당하는 것이 첫 번째 인자이며, Handler의 추가는 Stack 구조로 쌓이게 된다. 그리고 두 번째 인자는 Handler에 사용될 인자로써 이용된다.
Handler의 실행이 언제 이뤄지는지는 참고 부분에 기재되어 있다.

반환 값

아무런 값도 반환하지 않는다.

참고

HandlerStack 형태로 추가되며, pthread_clean_pop이라는 함수를 통해 Stack에서 제거된다. 이는 쓰레드가 pthread_exit으로 종료되거나 pthread_cancel을 통해 취소될 경우 자동으로 호출된다. 혹은 pthread_clean_pop의 인자를 0이 아닌 값을 넣으면 가장 최근에 추가한 Handler를 제거하면서 해당 Handler를 실행한다. 단, pthread_clean_push로 추가한 Handler들은 쓰레드의 작업을 수행하는 함수가 return으로 종료되었을 때는 실행되지 않는다.
Handler의 역할은 pthread_exitpthread_cancel 함수와 관련지어 이해할 수 있다. 쓰레드의 작업 함수가 끝까지 수행되기 전에 종료하게 되면, 정리가 필요한 작업들은 Handler를 통해 원하는 작업을 수행할 수 있다. 따라서 쓰레드 간 의존성이 있는 경우에는 쓰레드의 취소 등으로 종료되더라도 즉시 자원을 반납하지 않고 남은 작업을 수행할 수 있도록 하는 것이 가능하다.

5) pthread_cleanup_pop

함수 원형

void pthread_cleanup_pop(int execute);
C
복사

함수 인자

execute의 값이 0이라면 단순히 Stack에서 Handler를 제거하기만 하고, execute의 값이 0이 아니라면 제거할 Handler를 실행한 후 제거한다.

반환 값

아무런 값도 반환하지 않는다.

참고

pthread_cleanup_pop 함수의 용도는 pthread_cleanup_push로 추가된 Handler를 제거할 때 이용된다. HandlerStack 구조로 유지되므로 pthread_cleanup_pop이 호출되면 가장 최근에 추가된 Handler가 삭제된다.

6) pthread_exit

함수 원형

noreturn void pthread_exit(void *retval);
C
복사

함수 인자

pthread_exit 함수를 호출한 쓰레드를 즉시 종료하게 되는데, 이 때 retval이라는 포인터는 반환 값을 전달하는데 이용된다.

반환 값

noreturn 키워드에 따라 pthread_exit 함수는 그 어떤 값도 반환하지 않는다.

참고

Detached 쓰레드는 독립적인 쓰레드로 운용되므로 retval을 이용하더라도 반환 값을 받을 쓰레드가 없디. 따라서 Joinable 쓰레드에 대해서만 retval을 이용하여 pthread_join을 통해 반환 값을 받아 사용하게 된다. 따라서 retval의 이용은 쓰레드가 작업을 시작한 함수에서 return을 통해 반환하는 행위 동일하다.
다만 pthread_exit과 일반적인 return에 의한 종료에 대해서는 차이가 있다. 우선 pthread_exit 자체는 쓰레드가 작업을 시작한 함수에서 호출될 필요가 없음에 따라 원하는 지점에서 쓰레드의 종료를 만들 수 있다. 그리고 일반적인 return에 의한 종료에서는 pthread_cleanup_push로 등록된 Handler들의 호출하지 않지만, pthread_exit으로 종료된 경우에는 Handler들을 호출하면서 pthread_cleaup_pop을 수행한다.

7) pthread_cancel

함수 원형

int pthread_cancel(pthread_t thread);
C
복사

함수 인자

작업을 취소하고자 하는 쓰레드의 IDpthread_t 타입의 인자로 명시한다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

쓰레드가 수행하는 작업의 취소는 쓰레드의 설정 값에 따라 가능 여부가 갈린다. pthread_setcancelstate를 통해 PTHREAD_CANCEL_ENABLE 매크로로 쓰레드의 작업을 취소할 수 있도록 설정된 경우에 pthread_cancel 함수의 호출이 가능하다.
PTHREAD_CANCEL_ENABLE은 별도의 설정 없이 쓰레드를 생성했다면 기본 값으로 지정되어 있다.
만일 PTHREAD_CANCEL_DISABLE로 되어 있는 경우에는 pthread_cancel의 호출을 무시하고 넘기는 것이 아니라 별도의 Queue에 보관하여 PTHREAD_CANCEL_ENABLE이 되었을 때 Queue 내에 유지되는 작업 취소들을 순차적으로 처리한다.
일단 쓰레드 작업 취소를 허용했다면, 취소에 대한 유형은 2가지로 나뉜다. PTHREAD_CANCEL_DEFEREEDPTHREAD_CANCEL_ASYNCHRONOUS 유형이 존재하며, pthread_setcanceltype이라는 함수를 통해 설정할 수 있다. 두 값은 이름에서 알 수 있듯이 쓰레드의 작업 취소를 미루는 식으로 할지 비동기적으로 할지로 나뉜다. PTHREAD_CANCEL_DEFERED로 되어 있는 경우에는 쓰레드의 작업을 즉시 취소하는 것이 아니라, Cancellation Point로 정의된 특정 함수가 호출될 때까지 쓰레드의 작업을 취소하지 않는다.
Cancellation Point로 사용될 수 있는 함수들은 pthread 매뉴얼에 검색해보면 그 목록을 확인할 수 있으며, 별도의 설정이 없다면 PTHREAD_CANCEL_DEFERED가 기본 값으로 지정되어 있다.
PTHREAD_CANCEL_ASYNCHRONOUS로 되어 있는 경우에는 비동기적으로 동작함에 따라 쓰레드 작업을 즉시 취소할 수 있다. 다만, 해당 값을 이용하는 경우에는 시스템에 의해 보장되지 않는 작업일 수 있다는 점을 유의해야 한다. 따라서 기본 값인 PTHREAD_CANCEL_DEFERED를 통해 즉시 취소를 구현하여 이용하는 것이 일반적이다. PTHREAD_CANCEL_DEFERED를 이용하여 즉시 취소를 유도할 수 있는 방법은 시그널에 따른 동작을 사용자가 정의하여 Cancellation Point를 호출하도록 만들어 비동기적인 실행을 만드는 것이다.
쓰레드의 작업을 취소할 수 있고 이에 따른 처리 유형이 결정되었다면, 쓰레드가 취소 되었을 때는 pthread_cleanup_push로 등록된 Handler들을 실행하면서 제거하여 쓰레드가 종료된다.

8) pthread_self

함수 원형

pthread_t pthread_self(void);
C
복사

함수 인자

인자를 받지 않고 함수를 수행한다.

반환 값

프로세스 내의 쓰레드를 구분할 수 있는 pthread_t 타입의 ID 값을 반환한다.

참고

pthread_self는 쓰레드 내에서 호출하는 함수이며, 이 때 반환되는 값은 pthread_create의 첫 번째 인자인 포인터 값으로 할당되는 쓰레드의 ID 값과 동일한 값이다.

9) pthread_equal

함수 원형

int pthread_equal(pthread_t t1, pthread_t t2);
C
복사

함수 인자

비교하고자 하는 두 쓰레드의 ID 값을 인자로 받는다.

반환 값

인자로 받았던 두 pthread_t가 서로 다르다면 0을 반환하고, 그렇지 않다면 0이 아닌 값을 반환한다.

참고

pthread_t가 정수 값으로 정의된 시스템도 있겠지만, 그렇지 않은 시스템도 있다. 이는 pthread_t가 라이브러리 단위에서 명확히 정의된 것이 아니라 시스템 단위의 정의된 것을 이용하기 때문이다. 이 때 pthread_t에 대한 시스템 단위의 정의는 숨겨져 있고 라이브러리 상에서는 선언만 확인할 수 있는데, 이와 같은 선언을 Forward Declaration이라 하며 pthread_t와 같은 타입을 Opaque Type이라 한다. 따라서 연산자를 통한 pthread_t 간의 명확한 비교가 불가능하기 때문에 쓰레드의 비교 시에는 pthread_equal 함수의 이용이 불가피하다.
Opaque Type은 명확한 정의를 숨김에 따라 C 언어C++과 같이 객체 지향을 지원하도록 만들고자 할 때 캡슐화 (Encapsulation)을 구현하는데 이용하기도 한다. 이는 시스템마다 정의가 다를 수 있다는 점 때문에 Implementation Defined와 헷갈릴 수 있는데, Implementation Defined는 정의를 숨기지 않지만 Opaque Type은 정의를 숨기는데 그 목적이 있으므로 차이가 있다.

10) pthread_kill

함수 원형

int pthread_kill(pthread_t thread, int sig);
C
복사

함수 인자

대상이 되는 쓰레드를 pthread_t 타입의 ID로 명시한다. 그리고 보내려는 시그널을 sig로 명시한다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

pthread_kill 함수는 이름 때문에 pthread_exit과 용도를 헷갈릴 수 있는데, 해당 함수는 일반적인 프로세스에서 kill 함수가 수행하는 것과 동일한 역할을 맡는다. 비록 시그널에 대한 동작 수행을 쓰레드 내에서 처리하더라도 정지 혹은 재개, 종료에 대한 동작의 결과는 프로세스 단위로 이뤄진다. 그 외의 동작은 쓰레드 단위로 이뤄진다.
만일 pthread_kill의 인자로 존재하지 않는 pthread_t를 주게 되면, POSIX에서는 Undefined Behavior로 인식함에 따라 Segmentation Fault를 야기할 수 있다. 그 외에 glibc 같은 환경에서는 pthread_t를 못 찾았다는 ESRCH라는 값을 반환하도록 되어 있다.

3. Mutex

mutex도 쓰레드와 같이 속성에 대해 다양한 설정을 하는 것이 가능하다. mutex유형 (Type) 이라든가 강건성 (Robustness) 등의 설정이 가능한데, 여기서는 mutex의 속성까지는 다루지 않는다. 따라서 필요하다면 pthread_mutexattr_t를 찾아보는 것을 권장한다. mutex의 유형 및 강건성에 대해서는 간단하게 언급된다.

1) pthread_mutex_init

함수 원형

int pthread_mutex_init(pthread_mutex_t *restrict, const pthread_mutexattr_t *restrict);
C
복사

함수 인자

쓰레드 내에서 사용할 mutex를 할당 받기 위해 pthread_mutex_t의 주소를 받는다. 또한 mutex의 속성을 결정 짓기 위해 해당 설정 값들이 기록된 pthread_mutexattr_t의 주소를 인자로 받는다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

설정할 속성이 없다면 pthread_mutexattr_t의 주소에 대해서 NULL을 주어도 된다. 이 때는 시스템 기본 속성으로 mutex를 생성하게 된다.
생성된 pthread_mutex_t의 이용이 끝났다면 반드시 pthread_mutex_destroy를 이용하여 mutex를 해제해야 한다. 이미 해제된 mutex에 대해서 pthread_mutex_init의 호출은 문제 없지만, 이미 할당된 mutex에 대해 pthread_mutex_init을 다시 호출하는 행위는 Undefined Behavior이다.
pthread_mutex_init을 호출하는 방법 외에도 PTHREAD_MUTEX_INITIALIZERpthread_mutex_t의 선언 시 할당하는 방법도 있다. 이를 통한 mutex의 할당은 pthread_mutexattr_t를 이용하지 않기 때문에 mutex의 속성을 결정 지을 수 없으므로 시스템 기본 속성으로 mutex를 생성하게 된다. 비록 정적으로 할당된 것처럼 보이긴 하나 그 결과는 pthread_mutex_init에 속성 값을 NULL로 넣은 것과 동일하게 동적인 할당으로 이뤄진다.
두 할당 방법을 비교해보면, pthread_mutex_init은 반환 값을 통해 mutex 할당에 문제가 있었는지 확인할 수 있는 반면에 PTHREAD_MUTEX_INITIALIZER는 별도의 문제 확인이 동반되지 않는다. 따라서 디버깅 및 개발 단계에서는 pthread_mutex_init을 통해 검증을 하고, 검증이 요구되지 않는 상황에서는 PTHREAD_MUTEX_INITIALIZER를 쓰는 등 상황에 맞는 선택을 하면 된다.
PTHREAD_MUTEX_INITIALIZER에서 문제가 생겨 이를 이용하게 된다면, 이는 곧 Undefined Behavior로 이어진다.
만일 PTHREAD_MUTEX_INITIALIZER를 이용할 것이라면 선언 이후에 해당 값을 할당하는 식으로는 정상적으로 mutex를 할당받을 수 없다는 점에 유의해야 한다.
생성할 수 있는 mutex에 대한 제한은 고려되었지만, 사용하려는 메모리 크기 및 각 프로그램마다 요구하는 mutex의 수가 상황마다 다를 수 있기 때문에 반려되었다.

2) pthread_mutex_destroy

함수 원형

int pthread_mutex_destroy(pthread_mutex_t *);
C
복사

함수 인자

해제하고자 하는 pthread_mutex_t 타입의 mutex에 대한 주소를 받는다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

반드시 unlcokmutex에 대해서만 pthread_mutex_destroy를 호출할 수 있도록 한다. lock이 되어 있는 상태에서 mutex를 해제하는 행위는 Undefined Behavior이다. 또한 pthread_cond_timewait 혹은 pthread_cond_wait과 같은 함수에서 이용하고 있는 mutex를 해제하는 행위 역시 Undefined Behavior이다.

3) pthread_mutex_trylock

함수 원형

int pthread_mutex_trylock(pthread_mutex_t *);
C
복사

함수 인자

얻고자 하는 pthread_mutex_t 타입의 mutex에 대한 주소를 인자로 받는다.

반환 값

mutex를 얻을 수 있다면 mutex를 취득한 뒤 0을 반환하고, mutex를 얻을 수 없다면 Busy Waiting 이나 Sleep을 만들지 않고 0이 아닌 값을 반환한다.

참고

mutex을 취득하는 과정에서 pthread_mutex_trylock을 호출하여 mutex을 잡을 수 없는 상황일 때, 기존에 취득한 mutex을 버리는 식으로 Deadlock을 방지하도록 처리되어 있다고 해보자. 예를 들어, 두 쓰레드 AABB가 있다고 해보자. 쓰레드 AAlock1을 취득한 상황일 때, lock2를 잡을 수 없다면 lock1을 반환하도록 되어 있다. 쓰레드 BBlock2를 취득한 상황일 때, lock1을 잡을 수 없다면 lock2를 반환하도록 되어 있다. 두 작업은 Deadlock에 빠질 것 같은 상황이라면 서로 갖고 있는 mutex을 반환함으로써 Deadlock을 방지하는 것을 볼 수 있는데, 두 쓰레드가 해당 작업을 동시에 수행할 경우 mutex를 주고 받는 행위를 반복하게 될 수 있는데 이를 Livelock이라고 한다. 해당 상황은 복도에 두 사람이 서로 비켜 주려는데 여전히 서로 대치하고 있는 상황과 동일하다. 따라서 pthread_mutex_trylock을 사용하더라도 제시된 상황에 대해 주의해야 하고, LivelockDeadlock을 방지하는 방법과 동일하게 Partial Order를 준수함으로써 방지할 수 있다.

4) pthread_mutex_lock

함수 원형

int pthread_mutex_lock(pthread_mutex_t *);
C
복사

함수 인자

얻고자 하는 pthread_mutex_t 타입의 mutex에 대한 주소를 인자로 받는다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

mutex를 얻을 수 있다면 mutex를 취득한 뒤 함수가 종료되고, mutex를 얻을 수 없는 상황이라면 pthread_mutex_trylock과는 달리 Sleep 상태로 대기했다가 mutex를 얻는다. mutex를 당장 얻을 수 있는지 없는지 여부가 중요하지 않음에도 pthread_mutex_trylock과 같이 int 값을 반환하는 이유는 사용하려는 mutex의 속성 값에 따라 pthread_mutex_lock의 문제 상황을 감별하기 위해서이다.
mutex의 속성 값 중에서는 mutex의 유형이 포함되는데, 이는 PTHREAD_MUTEX_NORMAL, PTHREAD_MUTEX_ERRORCHECK, PTHREAD_MUTEX_RECURSIVE, PTHREAD_MUTEX_DEFAULT와 같이 4가지로 나뉜다.
이름에서도 알 수 있듯이, 유형에 대한 별도의 속성 값 설정이 없다면 PTHREAD_MUTEX_DEFAULT가 기본 값으로 이용된다.
PTHREAD_MUTEX_NORMAL 유형이라면, 이미 사용 중인 mutex에 대해 다시 pthread_mutex_lock을 호출했을 때 Deadlock이 발생한다.
PTHREAD_MUTEX_ERRORCHECK 유형이라면, 이미 사용 중인 mutex에 대해 다시 pthread_mutex_lock을 호출했을 때 상황에 맞는 적절한 값을 반환하게 된다.
PTHREAD_MUTEX_RECURSIVE 유형이라면, 이미 사용 중인 mutex에 대해 다시 pthread_mutex_lock을 호출했을 때 문제가 되지 않는다.
PTHREAD_MUTEX_DEFAULT 유형이라면, 이미 사용 중인 mutex에 대해 다시 pthread_mutex_lock을 호출하는 행위는 나머지 3개의 유형 중 하나처럼 동작하거나 그렇지 않으면 Undefined Behavior가 된다.

5) pthread_mutex_unlock

함수 원형

int pthread_mutex_unlock(pthread_mutex_t *);
C
복사

함수 인자

반환하고자 하는 pthread_mutex_t 타입의 mutex에 대한 주소를 인자로 받는다.

반환 값

함수 호출이 정상적으로 동작했다면 0을 반환하고, 그렇지 않으면 0이 아닌 값을 반환한다.

참고

mutex를 갖고 있는 쓰레드 외의 쓰레드가 pthread_mutex_unlock을 호출하는 행위는 대상이 되는 mutex의 유형 및 강건성과 같은 속성 값에 따라 Undefined Behavior일 수 있고 문제 상황에 맞는 값이 pthread_mutex_unlock의 반환 값으로 나타날 수 있다. 대체적으로 PTHREAD_MUTEX_STALLED_NP로 설정된 mutexUndefined Behavior를 야기하고, PTHREAD_MUTEX_ROBUST_NP로 설정된 mutex는 문제 상황에 맞는 값을 반환함에 따라 PTHREAD_MUTEX_ROBUST_NP로 강건성이 보장된 mutex는 반드시 문제 상황에 대한 확인 절차를 거치는 것이 권장된다.
이는 pthread_mutex_unlock 외에도 pthread_mutex_trylockpthread_mutex_lock 모두에게 해당되며, 강건성에 대해서는 별도의 설정이 없으면 PTHREAD_MUTEX_STALLED_NP로 설정된다.
주어진 pthread_mutex_trylock, pthread_mutex_lock, pthread_mutex_unlock에 대해서 PTHREAD_MUTEX_ROBUST_NP의 속성을 가진 강건성이 보장된 mutex에 대해선 mutex를 갖고 있는 상태로 프로세스 혹은 쓰레드가 종료된 경우에 EOWNERDEAD라는 값을 반환하게 된다. 이 때 mutex 내부에는 현재 사용자 어플리케이션의 상태가 일관적이이 않다고 판단하여 비일관성이 기록된다. 따라서 어플리케이션의 복구 루틴이 존재하는 경우에는 pthread_mutex_consistent라는 함수를 통해 mutex의 비일관성을 일관성으로 수정하고 pthread_mutex_unlcok으로 mutex를 반납한 뒤에 이어서 작업을 진행해야 한다. 만일 어플리케이션의 복구 루틴이 없거나 작동되지 않는 경우에는 mutex의 비일관성을 수정할 필요가 없으므로 pthread_mutex_consistent의 호출이 불필요하고, pthread_mutex_unlcok만 호출하여 mutex를 반납하도록 한다.
mutex의 유형이 PTHREAD_MUTEX_RECURSIVE라면 pthread_mutex_unlockmutex를 반납하는 결과를 만들지는 않고, 내부적으로 유지되는 재귀 횟수 값을 하나 감소시키는 동작만 수행한다. PTHREAD_MUTEX_RECURSIVE에 대해서는 재귀 횟수 값이 0이 되면 mutex가 반납된다.

4. Reference