Busy waiting
- while( x != y );
Non-busy waiting 해결책
- Mutex를 잠그기
- 조건 x == y를 테스트
- 참이면 mutex를 잠금 해제하고 루프를 종료
- 거짓이면 스레드를 일시 중단하고 mutex를 잠금 해제
- 조건 x == y를 테스트
Mutex는 데이터에 대한 스레드 액세스를 제어하여 동기화를 구현하지만
- 조건 변수는 실제 데이터 값에 기반하여 스레드를 동기화할 수 있게 함.
만약 조건 변수가 없다면,
- 프로그래머는 계속해서 조건이 충족되었는지 확인하기 위해 스레드를 폴링(가능하면 임계 구역에서)
- 이는 스레드가 이 활동에 계속해서 바쁘게 차지되어 매우 자원 소모적일 수 있음.
- 조건 변수는 폴링 없이 동일한 목표를 달성하기 위한 방법
- 조건 변수는 항상 Mutex 락과 함께 사용
조건 변수 : 임의의 조건이 참이 될 때까지 대기하는 프로세스 큐와 관련된 새로운 데이터 유형
pthread_cond_wait
- 조건 변수와 뮤텍스를 매개변수로 사용
- 호출된 스레드를 원자적으로 중단시키고 뮤텍스를 잠금 해제합니다.
- 스레드가 알림을 받으면 뮤텍스가 다시 획득
- 뮤텍스를 소유한 스레드만 호출해야
pthread_cond_signal
- 조건 변수를 매개변수로 사용하고 해당 대기 큐에서 대기 중인 스레드 중 하나를 깨움
- 스레드를 조건 변수 큐에서 뮤텍스 큐로 이동시키는 효과
조건 x == y를 사용하여 조건 변수 v와 뮤텍스 m으로 대기
pthread_mutex_lock(&m);
while( x != y )
pthread_cond_wait(&v, &m);
/* modify x or y if necessary */
pthread_mutex_unlock(&m)
대기 중인 스레드에게 x가 증가했음을 알리는 다른 스레드
pthread_mutex_lock(&m);
x++;
pthread_cond_signal(&v);
pthread_mutex_unlock(&m);
조건 변수 생성
#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;
pthread_cond_t
- 조건 변수를 나타내는 변수 형식
- 사용 전에 항상 초기화되어야 함
정적 변수의 초기화
- 단순히 'PTHREAD_COND_INITIALIZER'를 할당
동적으로 할당된 변수의 초기화
- pthread_cond_init 호출
- 속성 객체에 대한 'attr' 매개변수, 기본값은 NULL
성공 시 0, 실패 시 0이 아닌 오류 코드 반환
이미 초기화된 조건 변수의 초기화 →동작이 정의되어 있지 않음
조건 변수 파괴(destroying condition variables)
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cond);
성공 시 0, 실패 시 0이 아닌 오류 코드 반환
밑의 두 사례는 정의되지 않음
- 스레드가 파괴된 조건 변수를 참조하는 경우
- 다른 스레드가 차단된 상태에서 조건 변수를 파괴하려고 하는 경우
조건 변수에서 Signaling
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
pthread_cond_signal
- 'cond'가 가리키는 조건 변수에 대기 중인 스레드 중 최소한 하나를 깨움
pthread_cond_broadcast
- 'cond'가 가리키는 조건 변수에 대기 중인 모든 스레드를 깨웁니다.
성공 시 0, 실패 시 0이 아닌 오류 코드 return
스레드에서 신호 전달
하나의 프로세스에서 모든 스레드는 프로세스 신호 핸들러를 공유 각 스레드는 자신만의 신호 마스크(signal mask)를 가지고 있습니다.
비동기식(Asynchronous)
- 해당 신호가 차단되지 않은 어떤 스레드에 전달
동기식(Synchronous) • 해당 신호를 발생시킨 스레드에 전달됩니다.
지시식(Directed) • 식별된 스레드에 전달 (pthread_kill)
pthread_kill()
#include <signal.h>
#include <pthread.h>
int pthread_kill(pthread_t thread, int sig);
'sig' 신호 번호가 생성되어 'thread'로 지정된 스레드에 전달되도록 요청
성공 시 0, 실패 시 0이 아닌 오류 코드 return
pthread_mask()
#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이 아닌 경우, 함수는 *oset을 스레드의 이전 신호 마스크로 설정합니다.
성공 시 0, 실패 시 0이 아닌 오류 코드 return
멀티스레드 프로세스에서 신호 처리를 다룰 때
특정 스레드를 신호 처리에 할당 , 주 스레드는 모든 신호를 차단
할당된 스레드 생성 →해당 신호에 대해 sigwait()를 실행
(pthread_sigmask를 사용하여 신호를 차단 해제할 수 있음)
Reader-Writer Problem
리소스가 두 가지 유형의 액세스(읽기 및 쓰기)를 허용하는 상황에서
- 쓰기는 배타적으로 허용되어야 하고
- 읽기는 공유될 수 있음
두 가지 일반적인 전략
- 강력한 reader 동기화(Strong reader synchronization)
- reader에게 우선권을 부여하여 현재 작성 중인 작성자가 없는 한 리더에게 액세스를 부여
- 강력한 writer 동기화
- writer에게 우선권을 부여하여 모든 대기 중이거나 활성인 writer가 완료될 때까지 reader를 지연
POSIX는 읽기-쓰기 잠금을 제공 – 작성자가 잠금에서 차단되어 있는 경우 reader가 잠금을 획득할지 여부는 구현에 따라 다름
read-write locks 초기화 - pthread_rwlock_init()
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
읽기-쓰기 잠금을 나타내는 변수 형식
- 사용 전에 항상 초기화되어야 함
- 'attr'
- 읽기-쓰기 잠금 속성 객체를 가리키는 포인터
성공 시 0, 실패 시 0이 아닌 오류 코드 return
이미 초기화된 읽기-쓰기 잠금의 초기화 → 동작이 정의되어 있지 않음
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);
rdlock/tryrdlock
- 스레드가 읽기용으로 읽기-쓰기 잠금을 획득할 수 있게 한다.
wrlock/trywrlock
- 스레드가 쓰기용으로 읽기-쓰기 잠금을 획득할 수 있게 한다.
unlock
- 잠금을 해제한다.
성공 시 0, 실패 시 0이 아닌 오류 return
tryrdlock 및 trywrlock은 잠금이 이미 보유되어 있기 때문에 잠금을 획득할 수 없는 경우 EBUSY를 반환
tryrdlock 함수는 **rdlock**과 비슷하지만, 다른 스레드에서 쓰기용으로 해당 잠금을 보유하고 있을 때 대기하지 않는다.
- 만약 다른 스레드에서 쓰기용으로 잠금을 보유하고 있다면, **tryrdlock**은 즉시 실패하고 오류 코드를 반환
스레드가 wrlock으로 이미 획득한 잠금에 대해 rdlock을 호출하는 경우 →데드락이 발생할 수도
strerror()
- thread-safe하지 않은 몇 가지 함수 중 하나
- strerror()이 동시에 사용된다면 뮤텍스 잠금으로 보호해야 함
- perror()나 strerror() 둘 다 async-signal 안전하지 않음
해결책
- 동기화를 래퍼로 캡슐화(encapsulate the synchronization in a wrapper)
- perror_r()과 strerror_r()은 둘 다 thread-safe하고 async-signal 안전
- strerror이 사용하는 정적 버퍼에 대한 동시 액세스를 방지하기 위해 뮤텍스 사용
- perror()도 동일한 뮤텍스로 보호하여 동시 실행을 방지
- 뮤텍스를 잠그기 전에 모든 시그널을 차단
- 그렇지 않으면 시그널 핸들러 내부에서 이러한 함수 중 하나를 호출하면 데드락이 발생할 수 있음
데드락과 다른 까다로운 문제
동기화 구조를 사용하는 프로그램은 POSIX 기본 표준의 구현에서 감지되지 않을 수 있는 데드락 가능성이 있다.
스레드가 이미 보유한 뮤텍스에 대해 pthread_mutex_lock을 실행하는 경우
- pthread_mutex_lock은 실패하고 EDEADLK를 반환할 수 있지만, 표준(standard)는 이 함수가 그렇게 해야 한다고 요구하지 않는다.
락을 보유한 스레드가 오류를 만나는 경우
- 반환하기 전에 락을 해제해야 한다.
우선순위가 있는 스레드 (우선순위 역전)
- 낮은 우선순위 스레드가 뮤텍스를 보유한 상태에서 긴 시간동안 실행되는 중간 우선순위의 통신 스레드가 가끔씩 선점되어 높은 우선순위의 스레드가 긴 시간 동안 지연되는 상황이 발생할 수 있다.
'Study > 시스템프로그래밍' 카테고리의 다른 글
[시스템프로그래밍] 임계구역과 세마포어에 관하여 (1) | 2023.12.05 |
---|---|
[시스템프로그래밍] Mutex에 관하여 (4) | 2023.12.05 |
[시스템프로그래밍] 스레드 (0) | 2023.12.04 |
[시스템프로그래밍] 시간과 타이머 (0) | 2023.12.03 |
[시스템프로그래밍] Signals (1) | 2023.12.03 |