Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
274 changes: 274 additions & 0 deletions OS/Soojeong/Deadlock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
### 데드락 (Deadlock)

데드락, 즉 `교착 상태`를 한마디로 정의하면 “이러지도 저러지도 못하는 꽉 막힌 상태"예요. 하지만 이를 제대로 이해하려면 먼저 우리 시스템에서 작업이 어떻게 돌아가는지 알아야 해요. 프로세스에 대해 정리한 지 시간이 많이 지나, 프로세스와 스레드에 대해 간단하게만 정리하고 넘어갈게요.

### 프로세스와 스레드: 작업의 기본 단위

프로그램이 실행되는 기본 단위 두가지를 비교해볼게요.

| **구분** | **프로세스 (Process)** | **스레드 (Thread)** |
| ------------- | ----------------------------------- | ------------------------------------- |
| **정의** | 실행 중인 프로그램의 독립적인 단위 | 프로세스 내부의 작업 흐름 |
| **자원 공유** | 독립된 메모리 영역 (공유 X) | Stack을 제외한 영역을 서로 공유 |
| **장점** | 하나가 죽어도 다른 쪽에 영향이 적음 | 응답 속도가 빠르고 효율적인 자원 사용 |
| **단점** | 컨텍스트 스위칭 비용이 높음 | 동기화 문제 및 데드락 위험 존재 |

<aside>

왜 스레드는 더 빠를까?

스레드는 한 집 (프로세스)에 살며 Heap, Data, Code를 공유하는 가족과 같아요.

- 효율적인 통신: 방을 옮겨 다닐 때 짐을 새로 쌀 필요가 없습니다. 즉, 메모리 재할당이 필요 없습니다.
- 가벼운 전환: 다음 일꾼으로 교체할 때 확인해야 할 정보가 훨씬 적어 빠릅니다.
</aside>

<aside>

멀티스레드의 문제점

‘왜 스레드는 더 빠를까?’에서 스레드는 공유자원을 사용해 더 빠르고 효율적이라고 했는데, 그만큼의 위험도 따라요. 여러 명이 하나의 공유자원에서 동작할 때, 순서를 정하지 않으면 사고가 나는 것과 같아요.

- 경쟁 상태 (Race Condition): 두 스레드가 하나의 데이터를 동시에 수정하려다 데이터가 꼬이는 현상
- 동기화 (Synchronization): 레이스 컨디션을 막기 위해 한 번에 한 스레드만 자원을 쓰도록 lock을 거는 것
- 데드락 (Deadlock): 동기화를 잘못 사용했을 때 발생하며, 서로 상대방의 lock을 기다리다 영원히 멈춰버리는 현상
</aside>

### 데드락은 동기화 도구를 잘못 써서 생기는 문제인가 ?

```
공유 자원
-> 동시에 접근하면 위험
-> 그래서 lock을 건다 (동기화)
-> lock이 꼬이면 데드락
```

데드락을 알기 위해서는 이 흐름을 알아야 해요.

1. 경쟁 상태 (Race Condition)

- 여러 스레드가 같은 자원을 동시에 접근하는 상황
- 실행 순서에 따라 매법 결과가 달라지는 불안정한 상태가 됨
⇒ 여러 스레드가 동시에 접근하지 못하게 막아야겠다 ! (lock의 필요성)

1. 임계 구역 (Critical Section)

- 공유 자원에 접근하는 위험한 코드 구간
- 이 구간은 반드시 한 번에 하나의 스레드만 실행해야 함
⇒ 이 구역의 입구에 lock을 걸어두면 되겠다.

1. 동기화 필요성

- lock을 걸면 속도가 느려지는데 꼭 필요한가 ?
⇒ lock을 걸지 않으면 데이터가 깨짐
⇒ 느린 것보단 틀린 게 더 문제가 됨

1. Mutex vs Semaphore (lock을 거는 방법)

- Mutex: 한 명만 가질 수 있는 열쇠, 열쇠를 가진 스레드만 임계 구역에 들어갈 수 있음
```
a 스레드가 lock
-> a만 unlock 가능
-> b가 대신 unlock 불가
```
- Semaphore: 여러 명이 사용 가능한 번호표, 허용된 개수만큼 스레드가 들어갈 수 있음

```
프린터 3대, 스레드 10개
-> 최대 3명이 프린터 동시 사용 가능
-> 누가 어떤 프린터 사용하는지는 중요하지 않음
=> semaphore = 3

스레드가 프린터 사용하려고 할 때,
wait()
-> 남은 프린터 개수 1 감소
-> 남은 프린터 개수가 0 보다 크면 바로 프린터 사용이 가능
-> 남은 프린터 개수가 0이면 대기

다 사용하고 나오면,
signal()
-> 남은 프린터 개수 1 증가
-> 대기 중이던 스레드를 하나 깨움

------
signal을 누가 했는지 중요하지 않음
-> Semaphore는 이 signal이 누가 들어가서 썼는지 추적하지 않음
```

- Mutex = 이 방은 지금 누가 쓰고 있나?
- Semaphore = 이 건물에 몇 명까지 들어올 수 있나?

⇒ Mutex는 소유 개념이 있고, Semaphore는 소유 개념 없이 개수만 관리

```
공유 자원이 존재
-> 데이터 오염 방지를 위해 임계 구역 설정
-> lock(mutex/semaphore)을 걸어 순서 제어
-> 이 lock이 꼬여 서로가 서로를 기다리는
-> 데드락 발생
```

### 왜 lock이 꼬여 서로가 서로를 기다릴까 ?

데드락은 아래 설명할 4가지 조건이 단 하나도 빠짐없이 동시에 성립할 때만 발생해요. 반대로 말하면, 이 중 하나라도 깨지면 데드락은 성립되지 않아요.

1. 상호 배제 (Mutual Exclusion)

한 자원은 한 번에 하나의 프로세스(또는 스레드)만 사용할 수 있는 상태예요. 데이터가 꼬이는 걸 막기 위해 우리는 의도적으로 lock을 걸어 이런 상황을 만들었어요.

2. 점유와 대기 (Hold and Wait)

프로세스가 이미 가진 자원은 놓지 않은 채, 다른 자원이 풀리기만을 기다리는 상태예요.

3. 비선점 (No Preemption)

자원을 가진 프로세스로부터 강제로 자원을 빼앗을 수 없는 상태예요. 자원은 오직 자발적으로 반납할 때만 다시 사용가능해서, 다른 하나가 자원을 놓지 않으면 다른 쪽은 계속 기다릴 수 밖에 없어요.

4. 순환 대기 (Circular Wait)

대기하는 프로세스들이 원 형태를 이루는 상태예요.
a는 b의 자원을 기다리고,
b는 c의 자원을 가디라고,
c는 a를 기다리게 되면 아무도 움직일 수 없는 데드락이 완성돼요.
- 자원 할당 그래프 (Resource Allocation Graph)

데드락을 설명할 때 순환 대기를 시각적으로 보여주는 도구로,
프로세스는 원, 자원은 사각형으로 그려요.

화살표가 자원 -> 프로세스면 할당,
프로세스 -> 자원이면 대기예요.

이 그래프에서 사이클이 발견되면 데드락이 발생했거나 발생할 가능성이 있다는 뜻이에요.

### 어떻게 이 데드락 상태를 벗어날 수 있을까 ?

데드락을 해결하는 방법도 크게 4가지로 나눌 수 있어요.

1. 데드락 예방 (Prevention)

데드락 발생 조건 4가지 중 하나를 아예 발생하지 않도록 설계하는 방법이에요.
- 상호 배제 제거: 모든 자원을 공유하게 만드는 방식 (현실적으로 거의 불가능)
- 점유와 대기 제거: 자원이 필요한 경우, 한 번에 모든 자원을 요청하도록 강제함
- 비선점 제거: 자원을 점유한 프로세스가 다른 자원을 기다릴 때, 현재 가진 자원을 강제 회수함
- 순환 대기 제거: 자원에 순서를 매겨, 반드시 정해진 순서(낮은 번호 → 높은 번호)로만 요청하게 함

⇒ 특징: 데드락은 확실히 막지만, 자원 활용도가 낮아지고 비효율적이라는 단점이 있어요.

2. 데드락 회피 (Avoidance)

자원 할당 시 데드락 발생 가능성을 미리 계산하여 안전한 경우에만 자원을 할당하는 방법이에요.
- 은행원 알고리즘(Banker’s Algorithm): 자원을 할당했을 때 시스템이 '안전 상태(Safe State)'를 유지할 수 있는지 미리 검사해요. 안전하다면 할당, 아니면 대기해요.

⇒ 특징: 데드락을 유연하게 막을 수 있지만, 프로세스가 사용할 최대 자원량을 미리 알아야 한다는 제약이 있어요.
실제 프로그램이 실행 중에 얼마나 많은 메모리나 자원을 쓸지 예측하는 것은 어려워서 잘 쓰이지 않아요.

3. 데드락 탐지 및 회복 (Detection & Recovery)

데드락 발생을 허용하되, 시스템이 주기적으로 데드락 여부를 체크하여 발견 시 조치하는 방법이에요.
- 탐지: 자원 할당 그래프 등을 통해 현재 데드락이 발생했는지 감시
- 회복: 데드락에 빠진 프로세스를 강제 종료하거나, 필요한 자원을 강제로 회수(선점)하여 해결

⇒ 특징: 예방/회피보다 시스템 부담은 적지만, 작업이 중단되거나 데이터 손실 위험이 있어요.

4. 데드락 무시 (Ostrich Algorithm)

데드락 처리 비용이 발생 확률보다 크다고 판단될 때 사용하는 현실적인 대처법이에요.
- 타조 알고리즘: 타조가 모래에 머리를 박듯 문제를 무시

⇒ 현실: Windows, Linux 등 현대 운영체제는 데드락이 매우 드물게 발생하므로, 별도의 방지 로직을 두기보다 사용자가 직접 프로세스를 종료하게 함으로써 성능 이득을 챙기는 방법이에요.

### 식사하는 철학자

데드락을 검색하면 대표적으로 나오는 고전 문제예요.

> 5명의 철학자가 원탁에 앉아서 식사를 한다. 철학자들 사이에는 포크가 하나씩 놓여 있고, 철학자들은 다음의 과정을 통해 식사를 한다.
>
> 1. 일정 시간 생각을 한다.
> 2. 왼쪽 포크가 사용 가능해질 때까지 대기한다. 만약 사용 가능하다면 집어든다.
> 3. 오른쪽 포크가 사용 가능해질 때까지 대기한다. 만약 사용 가능하다면 집어든다.
> 4. 양쪽의 포크를 잡으면 일정 시간만큼 식사를 한다.
> 5. 오른쪽 포크를 내려놓는다.
> 6. 왼쪽 포크를 내려놓는다.
> 7. 다시 1번으로 돌아간다.

이 문제는 앞에서 정리한 데드락 발생 조건 4가지를 모두 만족하는 상황을 그대로 보여주는 예제예요.

1. 상호 배제 (Mutual Exclusion)

포크는 한 번에 한 철학자만 사용할 수 있어요.

즉, 포크는 **공유 자원이지만 동시에 사용될 수 없는 자원**이며, 이는 상호 배제 조건을 만족해요.

2. 점유와 대기 (Hold and Wait)

각 철학자는 왼쪽 포크를 이미 집어든 상태에서, 오른쪽 포크가 사용 가능해질 때까지 기다려요.

자원을 하나 점유한 채, 다른 자원을 기다리는 상황이에요.

3. 비선점 (No Preemption)

이미 다른 철학자가 집어든 포크를 강제로 빼앗을 수 있는 방법은 없어요.

포크는 오직 사용 중인 철학자가 내려놓아야만 다시 사용 가능해요.

4. 순환 대기 (Circular Wait)

모든 철학자가 동시에 왼쪽 포크를 집는 순간,

각 철학자는 오른쪽 포크를 기다리게 된다.

이때,
- 철학자 A는 B의 포크를 기다리고
- 철학자 B는 C의 포크를 기다리며
- …
- 마지막 철학자는 다시 A의 포크를 기다리는

원형 구조의 대기 관계가 형성되고, 결국 아무도 포크를 내려놓을 수 없는 데드락 상태가 돼요.

---

정리

- 데드락 한마디로

두 개 이상의 작업이 서로가 가진 자원을 기다리느라 둘 다 영원히 멈춰버린 상태를 말함
쉽게 비유하자면, 두 사람이 외나무다리에서 만났는데 서로 먼저 비키라고 요구하며 아무도 움직이지 못하는 상황

- 데드락 발생 조건

4가지 조건이 동시에 만족되어야 함

상호 배제(Mutual Exclusion): 자원은 한 번에 한 프로세스만 사용할 수 있음

점유와 대기(Hold and Wait): 자원을 가진 상태에서 다른 자원을 기다림

비선점(No Preemption): 다른 프로세스의 자원을 강제로 뺏을 수 없음

환형 대기(Circular Wait): 프로세스들이 원 형태로 서로의 자원을 대기함

- 가장 현실적인 데드락 해결 법

순환 대기 방지,
모든 자원에 순서를 정해두고 무조건 정해진 순서대로만 자원을 요청하게 만들면 서로 꼬리를 물 일이 없어서 데드락이 원천적으로 예방됨

- 데드락이 발생하면 시스템은 어떻게 대처할지

보통 두 가지 방법을 사용하는데, 데드락에 빠진 프로세스를 강제 종료하거나,
한쪽 프로세스가 가진 자원을 강제로 선점해서 다른 쪽에게 몰아주는 방식으로 해결

만약 중요도가 낮은 시스템이라면 그냥 무시하고 재부팅하기도 함

- 기아 상태, 데드락 차이

데드락은 프로세스들이 서로 자원을 붙잡은 채 기다리면서
아무도 앞으로 진행하지 못하는 완전히 멈춘 상태

기아 상태는
특정 프로세스의 우선순위가 낮아서
자원을 계속 못 받고 혼자만 계속 기다리는 불공평한 상태
-> 이때 시스템 자체는 정상적으로 동작할 수 있음

기아 상태는 오래 기다린 프로세스의 우선순위를 올려주는
에이징(Aging) 기법으로 해결 가능