공부/게임서버
교착 상태 (Deadlock)
samosa
2024. 11. 9. 22:21
Deadlock(교착 상태)는 두 개 이상의 스레드가 서로 다른 자원을 점유한 채, 상대방이 소유한 자원을 기다리며 무한 대기에 빠지는 상태를 말한다. 교착 상태가 발생하면 프로그램이 더 이상 진행되지 않으므로 매우 치명적이다.
Deadlock의 발생 조건
교착 상태가 발생하려면 다음 네 가지 조건이 동시에 만족되어야 한다.
- 상호 배제(Mutual Exclusion): 자원은 한 번에 하나의 스레드만 사용할 수 있다.
- 점유 대기(Hold and Wait): 최소한 하나의 스레드가 자원을 점유하고 있으면서 다른 자원을 기다린다.
- 비선점(Non-preemption): 스레드가 점유한 자원을 강제로 빼앗을 수 없다.
- 순환 대기(Circular Wait): 스레드 간의 자원 대기 관계가 원형으로 연결되어 있다.
Deadlock 예시 코드 (C#)
아래 코드는 두 개의 스레드가 각각 두 개의 자원을 잠근 후 상대방의 자원을 기다리면서 교착 상태에 빠지는 예시이다.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DeadlockExample
{
internal class Program
{
private static readonly object lock1 = new object();
private static readonly object lock2 = new object();
private static void Thread1()
{
lock (lock1)
{
Console.WriteLine("Thread 1: locked lock1");
Thread.Sleep(1000); // 일부러 지연을 줘서 deadlock 발생 가능성을 높임
lock (lock2)
{
Console.WriteLine("Thread 1: locked lock2");
}
}
}
private static void Thread2()
{
lock (lock2)
{
Console.WriteLine("Thread 2: locked lock2");
Thread.Sleep(1000); // 일부러 지연을 줘서 deadlock 발생 가능성을 높임
lock (lock1)
{
Console.WriteLine("Thread 2: locked lock1");
}
}
}
private static void Main(string[] args)
{
var t1 = new Task(Thread1);
var t2 = new Task(Thread2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2); // 프로그램이 deadlock에 빠지면 여기서 멈춤
}
}
}
문제 설명
- Thread1은
lock1
을 잠근 후lock2
를 잠그려고 시도한다. - Thread2는
lock2
를 잠근 후lock1
을 잠그려고 시도한다. - 결과적으로, Thread1은 Thread2가 잠근
lock2
를 기다리고, Thread2는 Thread1이 잠근lock1
을 기다리면서 교착 상태에 빠진다.
해결 방안
- 자원 획득 순서 고정: 모든 스레드가 자원을 잠글 때 동일한 순서로 잠그도록 한다. 예를 들어, 항상
lock1
을 먼저 잠그고 그다음lock2
를 잠그도록 코드를 작성하면 교착 상태를 방지할 수 있다. - 타임아웃 사용:
Monitor.TryEnter
를 사용하여 일정 시간 동안만 잠금을 시도하고, 그 시간이 지나면 실패하도록 한다. - 자원 계층 구조: 자원에 우선순위를 부여하고, 낮은 우선순위의 자원을 점유한 상태에서 높은 우선순위의 자원을 요청하지 않도록 설계한다.
교착 상태 방지 예시 코드
자원 획득 순서 고정을 통해 교착 상태를 방지하는 코드를 작성해보자.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace DeadlockPrevention
{
internal class Program
{
private static readonly object lock1 = new object();
private static readonly object lock2 = new object();
private static void Thread1()
{
lock (lock1)
{
Console.WriteLine("Thread 1: locked lock1");
Thread.Sleep(1000); // 지연
lock (lock2)
{
Console.WriteLine("Thread 1: locked lock2");
}
}
}
private static void Thread2()
{
// 자원 획득 순서를 Thread1과 동일하게 고정
lock (lock1)
{
Console.WriteLine("Thread 2: locked lock1");
Thread.Sleep(1000); // 지연
lock (lock2)
{
Console.WriteLine("Thread 2: locked lock2");
}
}
}
private static void Main(string[] args)
{
var t1 = new Task(Thread1);
var t2 = new Task(Thread2);
t1.Start();
t2.Start();
Task.WaitAll(t1, t2);
}
}
}
설명
Thread1
과Thread2
모두lock1
을 먼저 잠그고 그다음lock2
를 잠그는 순서를 사용하므로 교착 상태가 발생하지 않는다.- 자원 획득 순서를 고정하는 간단한 방법으로도 교착 상태를 예방할 수 있다.