Search
Duplicate
📒

[OS Stduy] 02-1. 동기화 문제와 해결방법(뮤텍스, 세마포어), 데드락 문제

상태
완료
수업
OS Study
주제
OS
연관 노트
3 more properties
참고

동기화 문제

NOTE
동기화는 하나의 객체를 여러 스레드가 접근할 때 생기는 문제를 해결하기 위해서 하나의 자원에 대한 처리 권한을 주거나, 순서를 조정하는 기법입니다.
복잡하고 복잡한 프로세스 제어~
동기화 문제는 주로 임계 구역 문제, 데드락, 경쟁 상태(Race Condition)와 같은 시나리오에서 주로 발생합니다. 동기화를 해결하는 방법으로 대표적인 방법은 4가지가 있습니다.
1.
스핀락(SpinLock): 스레드가 계속해서 반복적으로 잠금을 획득할 수 있는지 확인합니다.
2.
뮤텍스(Mutex): 단일 프로세스/스레드 만이 특정 코드 블록에 접근할 수 있도록 동기화합니다.
3.
세마포어(Semaphore): 임계 구역에 진입할 수 있는 프로세스나 스레드의 수를 제한하는 동기화 도구입니다.
4.
모니터(Monitor): 뮤텍스와 조건 변수를 결합하여 임계 구역을 관리하는 동기화 도구입니다.

스핀락

NOTE
스핀락은 잠금을 획들할 때 까지 계속해서 대기하며, 대기시간 동안 CPU 자원을 소모하게 되어 좋지 않은 방법입니다.
public class SpinLock { // CAS 연산을 위한 Atomic 클래스 사용 private final AtomicBoolean lock = new AtomicBoolean(false); public void lock() { log("락 획득 시도"); // 락 획득할 때 까지 스핀 대기(바쁜 대기) 한다. while (!lock.compareAndSet(false, true)) { log("락 획득 실패 - 스핀 대기"); } log("락 획득 완료"); } public void unlock() { lock.set(false); log("락 반납 완료"); } }
Java
복사

뮤텍스

NOTE
뮤텍스는 여러 스레드가 동시에 접근하려 할 때, 동시에 접근하지 못하도록 하는 동기화 도구 입니다. 자바에서는 ReentrantLock 클래스를 통해 구현 가능합니다.
뮤텍스
public class BankAccountV5 implements BankAccount { private int balance; private final Lock lock = new ReentrantLock(); public BankAccountV5(int balance) { this.balance = balance; } @Override public boolean withdraw(int amount) { log("거래 시작: " + getClass().getSimpleName()); // 잔고가 출금액 보다 적으면, 진행하면 안됨 if (!lock.tryLock()) { log("[진입 실패] 이미 처리중인 작업이 있습니다."); return false; } lock.lock(); // ReentratLock 사용 try{ // === 임계 영역 시작 === log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance); if (balance < amount) { log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance); return false; } // 잔고가 출금액 보다 많으면, 진행 log("[검증 완료] 출금액: " + amount + ", 잔액: " + balance); sleep(1000); // 출금시간 balance -= amount; log("[출금 완료] 출금액: " + amount + ", 잔액: " + balance); // === 임계 영역 종료 === } finally { lock.unlock(); } log("거래 종료"); return true; } @Override public synchronized int getBalance() { return balance; } }
Java
복사
ReentrantLock() 사용

모니터 락

NOTE
모니터 락은 뮤텍스의 일종으로 자바에서 synchronized를 블록을 통해 객체 단위의 잠금을 관리할 수 있으며, wait(), notify() 함수로 사용할 수 있습니다.
모니터 락
상호 배타 큐: 공유 자원에 하나의 프로세스만 진입하도록 하기 위한 큐입니다.
조건 동기 큐: 이미 공유 자원을 사용하고 있는 프로세스가 특정한 호출 wait()를 통해 조건동기 큐로 들어갈 수 있습니다.
public class BankAccountV2 implements BankAccount { private int balance; public BankAccountV2(int balance) { this.balance = balance; } @Override public synchronized boolean withdraw(int amount) { log("거래 시작: " + getClass().getSimpleName()); // 잔고가 출금액 보다 적으면, 진행하면 안됨 // === 임계 영역 시작 === log("[검증 시작] 출금액: " + amount + ", 잔액: " + balance); if (balance < amount) { log("[검증 실패] 출금액: " + amount + ", 잔액: " + balance); return false; } // 잔고가 출금액 보다 많으면, 진행 log("[검증 완료] 출금액: " + amount + ", 잔액: " + balance); sleep(1000); // 출금시간 balance -= amount; log("[출금 완료] 출금액: " + amount + ", 잔액: " + balance); // === 임계 영역 종료 === log("거래 종료"); return true; } @Override public synchronized int getBalance() { return balance; } }
Java
복사
synchronzied 사용 (모니터 락)

세마포어

NOTE
세마포어 방식은 동시에 접근할 수 있는 스레드의 수를 제한하는 동기화 도구입니다. 자바에서는 java.util.concurrent.Semaphore 클래스를 사용하여 구현할 수 있습니다.
저장된 개수만큼의 스레드가 동시에 접근가능하도록 해준다.
public class SemaphoreExample { // 한번에 2개의 스레드를 허용한다. private final Semaphore semaphore = new Semaphore(2); private int counter = 0; public void increment() { try { semaphore.acquire(); // 세마포어 획득 counter++; log("카운터 증가: " + counter); sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }finally { semaphore.release(); log("세마포어 종료"); } } public static void main(String[] args) throws InterruptedException { SemaphoreExample example = new SemaphoreExample(); Thread t1 = new Thread(() -> { log( "t1 시작"); example.increment(); log("t1 완료"); }, "Thread-1"); Thread t2 = new Thread(() -> { log( "t2 시작"); example.increment(); log("t2 완료"); }, "Thread-2"); Thread t3 = new Thread(() -> { log( "t3 시작"); example.increment(); log("t3 완료"); }, "Thread-3"); Thread t4 = new Thread(() -> { log( "t4 시작"); example.increment(); log("t4 완료"); }, "Thread-4"); t1.start(); t2.start(); t3.start(); t4.start(); t1.join(); t2.join(); t3.join(); t4.join(); log(example.semaphore.toString()); } }
Java
복사

교착상태(deadlock)

NOTE
교착상태는 다중 스레드에서 발생하는 문제로, 2개 이상의 작업이 서로 상대방이 소유한 자원을 기다리며 후한히 대기하는 상태에 빠져버리는 현상을 말합니다.
서로가 가지고 있는 자원을 기다리고있는중..
자원 할당이 원의 형태이면, 교착 상태가 발생한다.
교착상태가 발생하기 위해서는 아래 4가지 조건이 모두 만족되어야 합니다.
1.
상호 배제: 자원은 한 번에 한 프로세스만 사용할 수 있습니다. 즉 사용중인 자원을 다른 프로세스가 사용하지 못합니다.
2.
점유 대기: 최소한 하나의 자원을 점유한 상태에서 다른 자원을 추가로 요청하지만, 해당 자원이 다른 프로세스에 점유된 상태에서 대기해야 합니다.
3.
비선점: 이미 할당된 자원을 강제로 뺏을 수 없습니다.
4.
순환 대기: 자원과 프로세스의 집합에서 순환 형태로 프로세스들이 자원들을 대기합니다.

교착 상태 해결 기법

NOTE
교착 상태 해결에는 다양한 방법이 있다.

예방하기

교착 상태가 방생하는 4가지 조건 중 하나를 제거해서 예방할 수 있습니다. 하지만 4가지 방법 모두 완벽하지 않다는 단점이 있습니다.
1.
상호 배제를 제거한다: 모든 자원을 공유하게 된다.
2.
점유 대기를 제거한다: 필요한 자원을 모두 한 번에 할당해주게된다.
3.
비선점을 제거한다: 자원을 이용중인 프로세스가 자원을 뺏길 수 있다.
4.
원형 대기를 제거한다: 모든 자원 번호에 번호를 붙이는 방식으로 가능하지만 이 또한 활용률이 떨어질 수 있다.

회피하기

교착상태를 회피하기 위해서는 시스템이 자원 할당을 제어해야 합니다.
2번 프로세스에 최대를 채우는경우
2번 프로세스가 반환되면 다시 5개 여유가 생겨서, P1한테 다시 줄수도 있음
최종적인 순서열대로 할당해서 실행가능!
2번 프로세스에게 할당한다
2번이 끝나고 반환되어도, 1번과 3번둘다 요구를 들어줄 수 없음

검출 시 회복하기

시스템이 교착 상태를 탐지하고, 이를 회복하는 방법입니다.
프로세스 강제 종료: 교착 상태에 놓인 프로세스를 모두 종료한다.
선점을 통한 회복: 강제로 자원을 빼앗아 교착 상태를 해소합니다.