pthread
POSIX Thread
레디스 서버 교육 신청 |
레디스 정기점검/기술지원 Redis Technical Support |
레디스 엔터프라이즈 서버 Redis Enterprise Server |
---|
1. POSIX Thread: PThread
PThread는 "POSIX Thread"의 약자입니다. (발음: 포직스)
POSIX는 Portable Operating System Interface for Unix의 약자로,
Unix 계열 운영 체제에서 표준화된 API를 의미합니다.
따라서 pthread는 POSIX 표준에 따라 구현된 스레드 라이브러리를 의미합니다.
이 문서는
JOINC의 Pthread API Reference를 근간으로 pthread에 대해서 설명합니다.
Thread는 '스레드'로 통일합니다.
참고 자료:
1) Man Page: unix.com
2) Man Page: man7.org
- 2. 기본 스레드 함수
- 3. 스레드 동기화 함수
- 4. Thread Attribute 함수
- 5. 스레드 시그널 관련
- 6. 스레드 취소
- 7. Multi thread와 CPU 사용량
- 8. gcc 컴파일 방법
2. 기본 스레드 함수
스레드 생성과 종료에 관련된 가장 기본적인 함수들이다.
2.1. pthread_create
스레드 생성
스레드를 성공적으로 생성하면 0을 반환하고, 실패하면 오류 코드를 반환합니다.
void *(*start_routine)(void *), void *arg);
오류 코드
- EAGAIN: 시스템에 사용 가능한 스레드가 더 없는 경우 EAGAIN 오류 코드를 반환합니다. 시스템의 최대 스레드 수는 ulimit -l 명령을 사용하여 확인할 수 있습니다.
- EINVAL: attr 매개변수가 유효하지 않으면 EINVAL 오류 코드를 반환합니다. attr 매개변수는 pthread_attr_t 구조체를 가리킵니다. 이 구조체는 스레드의 속성을 지정합니다.
- EPERM: 프로세스가 스레드를 만들 권한이 없는 경우 EPERM 오류 코드를 반환합니다. 프로세스가 스레드를 만들 권한이 있는지 확인하려면 getrlimit() 함수를 사용하십시오.
- ENOMEM: 스레드를 만들기 위한 메모리가 부족하면 ENOMEM 오류 코드를 반환합니다. 시스템의 메모리 사용량을 확인하려면 top 명령을 사용하십시오.
참고(unix.com): pthread_create()
예제 코드: pthread_create.c
컴파일: gcc -o pthread_create pthread_create.c -lpthread
실행: $ ./pthread_create
결과(Case 1): int a = 3, b = 7; -> a가 종료되고 다음에 b가 종료된다.
1951987456 7: 0
1960380160 3: 1
1951987456 7: 1
1960380160 3: 2
1951987456 7: 2
1960380160 3: 3
1960380160 stop
1951987456 7: 3
p_thread[0]:1960380160 -> 3
1951987456 7: 4
1951987456 7: 5
1951987456 7: 6
1951987456 7: 7
1951987456 stop
p_thread[1]:1951987456 -> 7
결과(Case 2): int a = 7, b = 3; -> b가 먼저 종로되었으나 첫 번째 pthread_join()이 a 종료를 기다리고 있으므로 a가 종료될 때까지 기다렸다가 a, b가 순차적으로 처리된다.
96859904 3: 0
105252608 7: 1
96859904 3: 1
105252608 7: 2
96859904 3: 2
105252608 7: 3
96859904 3: 3
96859904 stop
105252608 7: 4
105252608 7: 5
105252608 7: 6
105252608 7: 7
105252608 stop
p_thread[0]:105252608 -> 7
p_thread[1]:96859904 -> 3
2.2. pthread_join
대상 스레드가 종료되기를 기다린다. 자원 해제
주요 기능
- 스레드 종료 대기: pthread_join() 함수는 대상 스레드가 종료될 때까지 호출 스레드를 블로킹합니다.
- 자원 회수: 대상 스레드가 종료되면 pthread_join() 함수는 스레드에 할당된 자원을 자동으로 회수합니다.
- 종료 상태 확인: pthread_join() 함수는 대상 스레드의 종료 상태를 반환합니다.
반환 값
- 0: 성공
- ESRCH: 대상 스레드가 존재하지 않습니다.
- EINVAL: thread가 유효한 스레드 ID가 아닙니다.
- EDEADLK: 호출 스레드가 이미 대상 스레드에 join된 상태입니다.
주의 사항
- pthread_join() 함수는 대상 스레드가 종료될 때까지 호출 스레드를 블로킹합니다. 따라서 pthread_join() 함수를 호출하기 전에 대상 스레드가 얼마나 오래 실행될지 예상하는 것이 중요합니다.
- pthread_join() 함수는 호출 스레드가 이미 대상 스레드에 join된 상태에서 다시 호출하면 오류가 발생합니다.
참고(joinc): pthread_join()
예제 코드: pthread_join.c
컴파일: gcc -o pthread_join pthread_join.c -lpthread
실행: $ ./pthread_join
2.3. pthread_detach
main 스레드에서 스레드를 분리시킨다. detach 하면 해당 스레드가 종료될 때 pthread_join 을 호출하지 않더라도 즉시 모든 자원이 해제(free)된다. malloc()으로 할당한 메모리는 별도로 free()해 주어야 한다.
참고: pthread_detach()
예제 코드: pthread_detach.c
컴파일: gcc -o pthread_detach pthread_detach.c -lpthread
실행: $ ./pthread_detach
pthread_detach()를 실행했을 때, pthread_detach()를 주석 처리했을 때로 구분해서
실행하고 아래 명령(ps)로 5번째 필드 메모리(VSZ) 사용량을 관찰한다.
VSZ: virtual memory size of the process in KiB (1024-byte units).
$ while [ 1 ]; do ps -aux | grep pthread | grep -v grep | grep -v vim; sleep 1; done
결과:
- Case 1-1: pthread_detach() 실행, char a[100000];
-> 스레드 함수가 종료되면 VSZ 메모리 사용량이 줄어야 하는데, 변화가 없다.
STAT가 'Sl+'는 스레드 함수가 실행중이고, 'S+'는 스레드 함수가 종료된 상태이다.
RSS는 스레드 함수가 종료되고 16kb 증가했다. - Case 1-2: pthread_detach()주석 처리, char a[100000]; -> VSZ 메모리 변화없다. RSS는 스레드 함수가 종료되고 16kb 증가했다.
- Case 2-1: pthread_detach() 실행, char a[1000000]; -> a를 10만에서 100만으로 늘렸다. VSZ 변화없다.
- Case 2-2: pthread_detach()주석 처리, char a[1000000]; -> a 100만, VSZ 메모리 변화없다.
- S (interruptible Sleep (waiting for an event to complete)): 프로세스가 CPU를 기다리고 있는 상태입니다.
- R (Running or runnable (on run queue)): 프로세스가 CPU를 사용하고 있는 상태입니다.
- D (Uninterruptible sleep (usually IO)): 프로세스가 시스템 호출을 기다리고 있는 상태입니다.
- Z (defunct ("Zombie") process, terminated but not reaped by its parent): 프로세스가 종료되었지만 아직 자원을 해제하지 않은 상태입니다.
- T (sTopped by job control signal): 프로세스가 중지된 상태입니다.
- t (stopped by debugger during the tracing)
- W (Paging (not valid since the 2.6.xx kernel)): 프로세스가 메모리 페이지 교환을 기다리고 있는 상태입니다.
- X (dead (should never be seen))
- < (High-priority (not nice to other users)): 프로세스가 높은 우선 순위를 가지고 있는 상태입니다.
- N (low-priority (Nice to other users)): 프로세스가 낮은 우선 순위를 가지고 있는 상태입니다.
- L (has pages Locked into memory (for real-time and custom IO))
- s (is a session leader)
- l (is multi-threaded (using CLONE_THREAD, like NPTL pthreads do))
- + (is in the foreground process group)
- Sl+ : 프로세스가 대기중이고, multi-thread이다.
S: 프로세스가 CPU를 기다리고 있는 상태
l: multi-threaded
+: foreground process
- S+ : 프로세스가 대기중
S: 프로세스가 CPU를 기다리고 있는 상태
+: foreground process
2.4. pthread_exit
현재 실행중인 스레드를 종료한다.
pthread_cleanup_push 가 정의되어 있다면, pthread_exit 가 호출될경우 cleanup handler 가 호출된다.
보통 이 cleanup handler 은 메모리를 정리하는 등의 일을 하게 된다.
예제 코드: pthread_exit.c
컴파일: gcc -o pthread_exit pthread_exit.c -lpthread
실행: $ ./pthread_exit
결과:
loop 1
loop 2
loop 3
loop 4
thread join : 500
2.5. pthread_cleanup_push
cleanup handlers 는 주로 자원을 되돌려주거나, mutex 잠금등의 해제를 위한 용도로 사용된다.
또한 malloc 으로 할당받은 메모리, 열린 파일지정자를 닫기 위해서도 사용한다.
자동으로 되는 것은 아니고 코딩해야 한다.
예제 코드: pthread_cleanup.c
컴파일: gcc -o pthread_cleanup pthread_cleanup.c -lpthread
실행: $ ./pthread_cleanup
결과:
loop 1
loop 2
loop 3
loop 4
thread is clean up
resource free
thread join : 9
2.6. pthread_cleanup_pop
pthread_cleanup_push 와 함께 사용되며, install 된 cleanup handler 을 제거하기 위해서 사용된다.
pthread_cleanup_push 와 pthread_cleanup_pop 은 반드시 같은 함수내의 같은 레벨의 블럭에서 한쌍으로 사용해야 한다.
2.7. pthread_self
pthread_self를 호출하는 현재 스래드의 스레드 식별자를 되돌려준다.
예제 코드: pthread_self.c
컴파일: gcc -o pthread_self pthread_self.c -lpthread
실행: $ ./pthread_self
결과: printf() format에 따라 결과가 다르게 나온다.
printf(%d)
->1793332992
1784940288
->1784940288
->1784940288
printf(%lu)
1 -> 140222351066880
140222342674176
2 -> 140222342674176
3. 스레드 동기화 함수
기본적인 매커니즘은 세마포어와 비슷하다. busy wait 상태에 놓이지 않음을 보장한다.
뮤텍스 메커니즘의 특징: mutex MUTual EXclusion(상호 배제)
- Atomicity: mutex 잠금(lock)는 최소단위 연적(atomic operation) 으로 작동한다. 이말의 뜻은 하나의 스레드가 mutex 를 이용해서 잠금을 시도하는 도중에 다른 스레드가 mutex 잠금을 할수없도록 해준다는 뜻이다. 한번에 하나의 mutex 잠금을 하도록 보증해준다.
- Singularity: 만약 스레드가 mutex 잠금을 했다면, 잠금을 한 스레드(:12)가 mutex 잠금을 해제 하기 전까지 다른 어떠한 스레드도 mutex 잠금을 할수 없도록 보증해준다.
- Non-Busy Wait: 바쁜대기 상태에 놓이지 않는다는 뜻으로, 하나의 스레드가 mutex 잠금을 시도하는데 이미 다른 스레드가 mutex 잠금을 사용하고 있다면 이 스레드는 다른 스레드가 락을 해제하기전까지 해당 지점에 머물러 있으며 이동안은 어떠한 CPU 자원도 소비하지 않는다(이를테면 sleep).
3.1. pthread_mutex_init
mutex 객체를 초기화한다.
참고: pthread_mutex_init()
예제 코드: pthread_mutex_init.c
컴파일: gcc -o pthread_mutex_init pthread_mutex_init.c -lpthread
실행: $ ./pthread_mutex_init
결과:
mutex_unlock
mutex function End
mutex thread End
3.2. pthread_mutex_destroy
뮤텍스 객체 mutex 를 제거하기 위해서 사용된다. 이 mutex 는 반드시 unlock 상태이여야 한다.
3.3. pthread_mutex_lock
critcal section 에 들어가기 위해서 mutex lock 을 요청한다. 만약 이미 다른 스레드에서 mutex lock 를 얻어서 사용하고 있다면 다른 스레드에서 mutex lock(뮤텍스 잠금)을 해제할때까지(사용할수 있을때까지) 블럭 된다.
- pthread_mutex_lock()함수는 아래의 에러코드를 반환한다.
• EINVAL: 뮤텍스가 잘못 초기화 되었다.
• EDEADLK: 이미 잠금을 얻은 스레드가 다시 잠금을 요청할 때 (error checking 뮤텍스일 경우 사용할 수 있다)
- pthread_mutex_trylock()함수는 아래의 에러코드를 반환한다.
• EINVAL: 뮤텍스가 잘못 초기화 되었다.
• EBUSY: 뮤텍스가 잠겨 있어서 잠금을 얻을 수 없다.
예제 코드: pthread_mutex_lock.c
컴파일: g++ -std=c++11 -o pthread_mutex_lock pthread_mutex_lock.c -lpthread
• vector를 사용하고 있으므로 g++(C++)로 컴파일해야 한다. -std=c++11를 추가한다.
3.4. pthread_mutex_unlock
critical section 에서의 모든 작업을 마치고 mutex lock 을 돌려주기 위해서 사용한다.
3.5. pthread_cond_init
조건변수 (condition variable)cond를 초기화하기 위해서 사용한다.
3.6. pthread_cond_signal
조건변수 cond에 시그날을 보낸다. cond에서 기다리는(wait) 스레드가 있다면 스레드를 깨우게 된다(봉쇄가 풀림). 여러 개의 스레드가 기다리고 있다면 그중 하나의 스레드에게만 전달된다. 이때 어떤 스레드에게 신호가 전달될지는 알수 없다.
3.7. pthread_cond_boradcast
조건변수 cond에서 기다리는(wait) 모든 스레드에게 신호를 보내서, 깨운다는 점을 제외하고는 pthread_cond_signal과 동일하게 작동한다.
3.8. pthread_cond_wait
조건변수 cond를 통해서 신호가 전달될때까지 블럭된다. 블럭되기 전에 mutex 잠금을 자동으로 되돌려준다.
3.9. pthread_cond_timewait
시간체크가 가능해서 abstime시간동안 신호가 도착하지 않는다면 error 를 발생하면서 리턴한다.
const struct timespec *abstime);
3.10. pthread_cond_destroy
조건변수 cond에 대한 자원을 해제한다. destroy 함수를 호출하기 전에 어떤 스레드도 cond에서의 시그널을 기다리지 않는걸 확인해야 한다.
3.11. 예제 코드
pthread_mutex_lock.c
C++ 표준 라이브러리 vector를 사용하고 있으므로 C++(g++)로 컴파일해야 한다.
gcc로 컴파일하면 아래와 같은 에러가 발생한다.
gcc -o pthread_mutex_lock pthread_mutex_lock.c -lpthread
pthread_mutex_lock.c:36:10: fatal error: vector: 그런 파일이나 디렉터리가 없습니다
>> #include <vector>
아래와 같이 g++로 컴파일해야 한다.
$ g++ -std=c++11 -o pthread_mutex_lock pthread_mutex_lock.c -lpthread
4. Thread Attribute 함수
4.1. pthread_attr_init
thread attribute 객체인 attr을 디폴트 값으로 초기화 시킨다. 성공할 경우 0을 돌려주고 실패할 경우 -1을 되돌려준다.
4.2. pthread_attr_distroy
thread attribute 객체인 attr을 제거한다. 제거된 attr 을 다시 사용하기 위해서는 pthread_attr_init를 이용해서 다시 초기화 해주어야 한다.
4.3. pthread_attr_getscope
스레드가 어떤 영역(scope)에서 다루어지고 있는지를 얻어오기 위해서 사용된다. 리눅스의 경우 유저모드 스레드인데, 즉 커널에서 스레드를 스케쥴링하는 방식이 아닌 스레드 라이브러리를 통해서 스레드를 스케쥴링 하는 방식을 사용한다.
예제 코드: pthread_attr_getscope.c
컴파일: gcc -o pthread_attr_getscope pthread_attr_getscope.c -lpthread
실행: $ ./pthread_attr_getscope
결과:
4.4. pthread_attr_setscope
스레드가 어떤 영역(scope)에서 작동하게 할것인지 결정하기 위해서 사용한다. 리눅스의 경우 Kernel mode 스레드를 지원하지 않음
4.5. pthread_attr_getdetachstate
스레드가 join 가능한 상태(PTHREAD_CREATE_JOINABLE) 인지 detached 상태인지 (PTHREAD_CREATE_DETACHED) 인지를 알아낸다. 알아낸 값은 아규먼트 detachstate 에 저장된다.
예제 코드: pthread_attr_getdetachstate.c
컴파일: gcc -o pthread_attr_getdetachstate pthread_attr_getdetachstate.c -lpthread
실행: $ ./pthread_attr_getdetachstate
결과:
4.6. pthread_attr_setdetachstate
스레드의 상태를 PTHREAD_CREATE_JOINABLE 혹은 PTHREAD_CREATE_DETACHED 상태로 변경시키기 위해서 사용된다.
아래와 같은 방법으로 사용하면 된다.
// JOINABLE 상태로 변경하고자 할때
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
// DETACHED 상태로 변경하고자 할때
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
5. 스레드 시그널 관련
스레드간 프로세스와 스레드간 시그널 전달관련 API들이다.
5.1. pthread_sigmask
특정 스레드만 시그널을 받도록 하고 싶을 때 이 함수를 이용한다.
5.2. pthread_kill
스레드 식별번호 thread로 signo 번호의 시그널을 전달한다.
예제 코드
// thread1에 SIGINT 시그널 전송
pthread_kill(thread1, SIGINT);
// thread2에 SIGUSR1 시그널 전송
pthread_kill(thread2, SIGUSR1);
주의 사항
- pthread_kill() 함수는 같은 프로세스 내에 존재하는 스레드에만 시그널을 전송할 수 있습니다.
- SIGKILL 및 SIGSTOP 시그널은 pthread_kill() 함수를 통해 전송할 수 없습니다.
- 전송된 시그널에 대한 처리 방식은 스레드의 시그널 핸들러에 따라 결정됩니다.
- pthread_kill() 함수는 스레드의 실행 상태에 영향을 미치지 않습니다.
pthread_kill() 함수의 활용
- 스레드 종료: SIGKILL 또는 SIGTERM 시그널을 전송하여 스레드를 강제 종료할 수 있습니다.
- 스레드 동기화: 시그널을 사용하여 스레드 간 동기화를 구현할 수 있습니다.
- 스레드 상태 확인: 시그널을 사용하여 스레드의 현재 상태를 확인할 수 있습니다.
Man Page: pthread_kill()
5.3. sigwait
시그널 전달을 동기적으로 기다린다.
6. 스레드 취소
https://www.joinc.co.kr/w/Site/Thread/Advanced/ThreadCancle THREAD 취소와 종료
6.1. pthread_cancel
스레드에 취소 요청을 보낸다.
https://www.joinc.co.kr/w/man/3/pthread_cancel
6.2. pthread_setcancelstate
취소 상태는 이 함수에 의해 결정한다.
- PTHREAD_CANCEL_ENABLE 상태라면 스레드는 취소요청을 받아들이고 취소지점까지 진행한다음 취소 지점을 벗어나야지 종료한다. ENABLE 상태일 경우 별도로 취소지점까지 진행한다음 종료할 것인지 아니면 바로 종료할 것인지를 pthread_setcanceltype() 를 통해 지정할 수 있다.
- PTHREAD_CANCEL_DISABLE 스레드 취소 기능을 비활성화하는 매크로입니다.
• 중요한 작업을 수행하는 스레드가 취소되어서는 안 되는 경우
• 스레드가 취소 지점을 명확하게 정의하기 어려운 경우
• 스레드 취소 기능이 성능 저하를 초래하는 경우
- 예제 코드
- pthread_getcancelstate() get 함수는 없다.
6.3. pthread_setcanceltype
PTHREAD_CANCEL_ENABLE인 경우 취소의 종류를 결정할 수 있다.
- PTHREAD_CANCEL_ASYNCHRONOUS 바로 종료
- PTHREAD_CANCEL_DEFERRED 취소지점을 벗어날 때까지 기다린다.
- 취소지점으로 설정될 수 있는 영역은 다음과 같다.
• pthread_join(3)
• pthread_cond_wait(3)
• pthread_cond_timedwait(3)
• pthread_testcancel(3)
• sem_wait(3)
• sigwait(3)
6.4. pthread_testcancel
https://www.joinc.co.kr/w/Site/Thread/Advanced/ThreadCancle THREAD 취소와 종료
총 정리 예제 코드이다. (함수 순서는 조금 바꾸었다)
컴파일: gcc -o pthread_cancel pthread_cancel.c -lpthread
실행: $ ./pthread_cancel -> PTHREAD_CANCEL_ENABLE 실행
결과:
// pthread_cancel(pt)을 받으면 clean_up()을 실행한다.
Thread cancel Clean_up function -> sleep(5) 5초 있다가 pthread_cond_signal(&cond)받으면 바로 종료한다.
exit
실행: $ ./pthread_cancel -> PTHREAD_CANCEL_DISABLE 실행
결과:
NO WAIT COND
EXIT
exit
Multi thread와 CPU 사용량
스레드를 2개, 4개, 8개 생성해서 테스트했다.
4 threads
CPU가 거의 400% 사용(398%) 사용중이다. top과 ps 명령으로 확인
스레드별 CPU 사용량 확인
실행: 4 스레드 생성
pid를 지정해서 스레드 이름 확인
top 명령으로 스레드별 CPU 사용량 확인
while(1) { } 이렇게 while 안에 아무 문장이 없어도 CPU 100%를 사용하는지 테스트 -> CPU 100%를 사용한다.
8 threads
CPU는 사용량은 ps 보다 top이 정확하다.
컴파일 방법
Compile: gcc -o pthread_busy pthread_busy.c -lpthread -D_GNU_SOURCE
pthread_setname_np()를 사용하려면 '-D_GNU_SOURCE'이 필요합니다.
gcc 컴파일 방법
gcc 컴파일 방법과 g++(C++) 컴파일 방법을 설명합니다.
1. gcc 컴파일 방법
$ gcc -o my_program my_program.c -lpthread
pthread lib를 사용한다.
2. g++(C++) 컴파일 방법
- 컴파일러 플래그 설정
컴파일러에 -std=c++11 또는 -std=c++14 플래그를 추가해야 합니다.
이 플래그는 C++11 또는 C++14 표준을 사용하여 코드를 컴파일하도록 지시합니다.
vector는 C++11 표준에서 도입되었으므로 이 플래그를 설정하지 않으면 컴파일 오류가 발생합니다.
예를 들어, 다음과 같이 명령어를 사용하여 코드를 컴파일할 수 있습니다.
$ g++ -std=c++11 -o my_program my_program.c -lpthread - 헤더 파일 경로 설정
컴파일러가 vector 헤더 파일을 찾을 수 있도록 해야 합니다.
일반적으로 vector 헤더 파일은 /usr/include/c++/ 디렉토리에 설치됩니다.
하지만 설치된 위치가 다를 수 있으므로 시스템 설정을 확인해야 합니다.
컴파일러가 헤더 파일을 찾을 수 없는 경우 다음과 같이 -I 플래그를 사용하여 헤더 파일 경로를 지정할 수 있습니다.
$ g++ -std=c++11 -I/usr/include/c++/ -o my_program my_program.c -lpthread
<< Redis Threads | Redis Process >> |
---|