다중 프로그래밍 환경에서 대기중인 스레드가 요청한 자원을 함께 대기중인 스레드가 점유하고 있으며, 그 관계가 사이클을 형성하는 경우를 교착상태라고 한다.
요청: 스레드가 자원을 요청, 즉시 허용되지 않으면 대기
사용: 자원에 대해 작업
방출: 자원을 방출
한 스레드 집합 내의 모든 스레드가 그 집합 내의 다른 스레드에 의해서만 발생될 수 있는 이벤트를 기다린다면, 그 스레드 집합은 교착상태에 있다.
교착상태가 일어나는 예시는 다음과 같다.
void *do_work_one(void *param)
{
pthread_mutex_lock(&first_mutex);
pthread_mutex_lock(&second_mutex);
/* work */
pthread_mutex_unlock(&second_mutex);
pthread_mutex_unlock(&first_mutex);
}
void *do_work_two(void *param)
{
pthread_mutex_lock(&second_mutex);
pthread_mutex_lock(&first_mutex);
/* work */
pthread_mutex_unlock(&first_mutex);
pthread_mutex_unlock(&second_mutex);
}
스레드 두개가 각각 do_work_one, do_work_two 함수를 실행할 때,
thread1이 first_mutex를 획득하고, thread2가 second_mutex를 획득한 상태는 교착상태이다.
하지만 이렇게 구성한다고 해서 교착상태가 늘 일어나는 것은 아니다. thread1이 thread2보다 먼저 mutex를 모두 획득하고 방출한다면 교착상태는 일어나지 않는다. 이러한 이유 때문에 교착상태 식별 및 검사 작업이 어렵다.
라이브락은 또 다른 형태의 라이브니스 장애이다.
교착상태는 4가지 조건이 동시에 성립될 때 발생할 수 있다.
상호배제(mutual exclusion): 최소 하나의 자원이 상호배제를 만족하며 점유되어야 한다.
점유대기(hold-and-wait): 스레드가 최소 하나의 자원을 점유한 채, 다른 추가 자원을 대기해야 한다.
비선점(no preemption): 자원들을 강제로 방출할 수 없다. 사용중인 스레드에 의해 자발적으로만 방출될 수 있다.
순환대기(circular wait): 대기하고 있는 스레드간의 관계 그래프를 그렸을 때 사이클이 형성된다.
스레드를 T
, 자원을 R
이라고 할 때
T
->R
R
->T
교착상태를 무시하는 것은 점차 시스템 성능을 저하시키고 시스템을 정지하게 만들 수 있다. 하지만 교착상태의 발생확률은 매우 작은 편이다. 따라서 교착상태를 무시하는 것이 다른 처리 방법과 비교해 비용이 적게 든다.
교착상태의 예방은 교착상태가 발생하는데 필요한 조건 4가지 중 최소 하나가 성립하지 않음을 보장함으로써 이뤄진다.
따라서 상호배제 조건을 거부함으로써 교착상태 예방을 하는 것은 불가능하다.
스레드가 자원을 요청할 때는 자원을 점유하고 있지 않은 상태여야 한다. 즉 실행 전 필요한 모든 자원을 요청하고 할당해야 한다.
그냥 딱 봐도 현실적으로 적용하기 힘들 것 같다.
락 순서를 설정하는 것은 복잡한 시스템에서는 어려울 수 있다.
하지만 이 방법은 때때로 유용하다. (다른 3가지 예방과 달리.)
교착상태 예방은 장치의 이용률이 저하되고 시스템 총처리율이 감소한다.
자원 요청에 대한 추가정보를 이용하여 교착상태를 회피한다.
각 스레드의 요청과 방출에 대한 순서를 파악하고 있다면, 각 자원 요청에 대해서 가능한 미래의 교착상태를 피할 수 있다.
다양한 회피 알고리즘이 있으며, 각 알고리즘은 필요한 정보의 양과 유형에서 차이가 있다.
스레드 집합<T1,T2...,TN>를 특정 순서로 실행했을 때 교착상태를 야기시키지 않고 모두 실행할 수 있다면 그 순서를 안전순서라고 한다.
안전순서를 찾을 수 있다면 시스템은 안전상태이다.
시스템이 안전상태라면 교착상태가 아니다. 하지만 불안전 상태라고 해서 교착상태인 것은 아니다.
시스템은 안전상태에서 불안전 상태로 전이될 수 있다.
시스템은 안전상태를 유지하기 위해 스레드의 자원 요청을 보류할 수 있다. 따라서 스레드가 요청한 자원보다 많은 양을 보유한 상태가 지속될 수 있다. -> 자원의 이용률은 회피를 안 쓸 때에 비해서 낮아질 수밖에 없다.
종류마다 자원이 여러개면 사용할 수 없다.
자원할당 그래프 알고리즘보다는 효율성이 떨어지지만, 자원의 개수가 여러개여도 사용할 수 있다.
은행원 알고리즘에 사용되는 자료구조
N
개의 스레드와 M
개의 자원이 존재한다고 가정
Available[M]
: 자원의 종류별로 사용 가능한 개수
MAX[N][M]
: 각 스레드가 최대로 필요로 하는 자원의 종류별 개수
Allocation[N][M]
: 각 스레드에 현재 할당된 자원의 종류별 개수
Need[N][M]
: 각 스레드가 앞으로 필요로 하는 자원의 종류별 개수
- Need[i][j] = Max[i][j] - Allocation[i][j]
Work[][] = Available[][]
, Finish[i] = false
로 초기화 한다.
Finish = false
이고 Need < Work
인 i
를 찾는다.
i
값이 없다면 4번으로 간다.Work = Work + Allocation[i]
, Finish[i] = true
i
번째 작업을 완료했다고 가정하고 가용 자원에 i
가 가지고 있던 자원을 추가한다.모든 i
값에 대해 Finish[i] = true
이면 시스템은 안전상태에 있다.
M*N^2번의 연산이 필요하다.
특정 스레드가 자원을 요청했을 땐, 요청을 받아들였다고 가정하고 안전상태를 확인한다. 안전한 경우 자원을 할당해준다.
Request[i] <= Need[i]
를 만족하면 2번으로 간다.Request[i][j] <= Available[j]
이면 3번으로 간다.상태를 바꾼 후 안전성 알고리즘을 통해 안전상태를 확인한다.
자원할당 그래프의 변형을 통해 대기 그래프(wait-for graph)를 구성하고 이를 통해 교착상태를 탐지한다.
자원할당 그래프에서 T
->R
간선을 Ti
->Tj
의 형태로 변형한다.
- i번째 스레드가 j번째 스레드가 가진 자원을 대기하고 있다는 의미이다.
대기 그래프에서 사이클이 탐지 된다면 교착상태 가능성을 보고한다.
Need
대신 Request
행렬을 사용한다.8.7.2에서 다룬 탐지 알고리즘을 언제 사용하는지는 다음 사항에 따라 결정된다.
교착상태의 발생 빈도
교착 상태 발생시 규모(몇개의 스레드가 통상 연류되는가)
너무 자주 호출하면 오버헤드가 커진다.
교착상태를 깨뜨리는 데는 두 가지 방법이 있다.
1. 순환대기를 깨뜨리기 위해 단순히 한 개 이상의 스레드를 중지(abort)시키는 것이다.
2. 두 번째 방법은 교착 상태에 있는 하나 이상의 스레드들로부터 자원을 선점(preempt)하는 것이다.
프로세스를 중지시키기는 쉽지 않다. 프로세스가 파일을 갱신하는 도중이었다면, 그 시점에 종료시 파일은 부정확한 상태가 된다.
교착상태가 깨질 때까지 프로세스로부터 자원을 선점하여 다른 프로세스에 주어야 한다.
선점이 필요할 때 다음 세가지사항을 고려한다.
희생자 선택
후퇴
자원을 선점당한 프로세스는 정상적 실행히 불가능 -> 안전한 상태로 후퇴시키고, 다시 시작한다.
실행하는 모든 프로세스의 상태에 보다 많은 정보를 유지할 것을 필요로 한다.
기아 상태
와우 진짜 CS 탄탄하네유