반응형
프로세스 또는 스레드 간의 동기화와 공유 자원 관리를 위해 사용하는 동기화 도구
원리인가, 구현인가?
- 원리 측면:
- 뮤텍스와 세마포어는 동시성 제어 문제를 해결하기 위한 개념적인 원리
- 프로세스 간 공유 자원 접근 문제(Critical Section Problem)를 해결하기 위해 설계
- "P/V 연산", "락/언락" 등의 수학적 원리에 기반
- 구현 측면:
- 운영 체제는 뮤텍스와 세마포어를 시스템 콜로 구현하여 동기화를 제공
- 프로그래밍 언어에서는 이를 감싸는 고급 추상화 클래스/메서드로 구현
뮤텍스; 단일 스레드/프로세스가 임계 구역을 독점적으로 보호
특징
- 초기값이 0인 상태에서 스레드가 자원을 점유하면 값을 0 -> 1로 변경.
- 뮤텍스는 한 번에 하나의 스레드만 자원을 점유할 수 있도록 제한하는 도구
- 뮤텍스는 자원의 소유권 개념이 있어, 락을 획득한 스레드만 언락이 가능
- Java의 ReentrantLock
뮤텍스 초기값 0의 의미
- 0:
- 뮤텍스가 열려 있는 상태(사용 가능)
- 어떤 스레드도 해당 뮤텍스를 점유하고 있지 않은 상태
뮤텍스의 동작 과정
- 락(Lock):
- 스레드가 뮤텍스를 점유하려고 하면, 뮤텍스 값을 1로 변경
- 동시에 다른 스레드가 뮤텍스를 점유하려고 하면 대기 상태로 전환
- 언락(Unlock):
- 스레드가 작업을 완료하고 뮤텍스를 해제하면, 뮤텍스 값을 0으로
- 대기 중인 다른 스레드가 뮤텍스를 점유할 수 있도록 허용
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MutexExample {
private final Lock lock = new ReentrantLock();
public void criticalSection() {
lock.lock(); // 임계 구역 접근
try {
System.out.println(Thread.currentThread().getName() + " is in critical section.");
} finally {
lock.unlock(); // 접근 해제
}
}
/////
public static void main(String[] args) {
MutexExample example = new MutexExample();
Runnable task = example::criticalSection;
// 스레드 2개 실행
new Thread(task).start();
new Thread(task).start();
}
}
- 첫 번째 스레드가 실행되어 lock.lock()으로 락 획득
- 임계 구역에 진입해 메시지를 출력
- lock.unlock()로 락 해제
- 두 번째 스레드는 첫 번째 스레드가 락을 해제하기 전까지 대기
- 첫 번째 스레드가 락을 해제하면, 두 번째 스레드가 락을 획득하고 임계 구역에 진입
- 임계 구역 보호:
- ReentrantLock을 사용하여 임계 구역에 동시에 하나의 스레드만 접근하도록 보장
- 스레드 간 동기화:
- 두 스레드는 순차적으로 criticalSection 메서드에 접근하며, 동시에 실행되지 않음
- 락 해제 보장:
- try-finally 구조를 통해 락이 반드시 해제되도록 보장하여 데드락을 방지
세마포어; 여러 스레드/프로세스가 제한된 자원을 공유
특징
세마포어는 정수 값(카운터)을 기반으로 동작하며, 공유 자원에 대한 접근을 제어하는 데 사용; Java의 Semaphore
- 정수 값 기반 제어:
- 세마포어는 정수 값을 사용하여 현재 공유 자원에 접근할 수 있는 허용 가능한 스레드 또는 프로세스의 수를 나타냄
- 이 값은 초기화된 이후, 특정 연산을 통해 증가하거나 감소
- P 연산 (wait 또는 acquire):
- 세마포어 값을 감소
- 값이 0보다 작아질 경우, 현재 프로세스나 스레드는 대기 상태에 들어가고, 다른 스레드가 세마포어 값을 증가시킬 때까지 블록
- V 연산 (signal 또는 release):
- 세마포어 값을 증가
- 대기 중인 스레드가 있으면 이를 깨워서 실행
세마포어의 초기값
세마포어의 초기값은 관리할 자원의 개수를 나타냄
- 초기값 1: 바이너리 세마포어(Binaray Semaphore)처럼 동작하여 뮤텍스와 비슷한 역할
- 초기값 N (N > 1): 자원을 N개까지 동시 허용하는 카운팅 세마포어(Counting Semaphore) 역할
- 세마포어는 다음과 같은 연산으로 동작한다:
- P(Wait):
- 자원의 개수를 감소시킴.
- 값이 0 이하가 되면 스레드는 자원이 해제될 때까지 대기.
- V(Signal):
- 자원의 개수를 증가시킴.
- 대기 중인 스레드가 있다면 해제된 자원을 할당받아 실행을 재개.
- P(Wait):
세마포어의 값과 자원의 상태
- 값 > 0:
- 자원이 하나 이상 사용 가능한 상태
- 대기 중인 스레드가 즉시 자원에 접근 가능.
- 값 = 0:
- 모든 자원이 이미 사용 중
- 자원을 사용하려는 스레드는 대기 상태로 전환
- 값 < 0 (특정 구현에서 허용):
- 대기 중인 스레드의 수를 나타낼 수 있
- 하지만 일반적인 세마포어 구현에서는 음수값을 사용하지 않고 **대기열(queue)**로 관리
세마포어의 용도
- 임계 구역(Critical Section) 보호:
- 공유 자원에 대한 동시 접근을 제어하여 데이터 무결성을 보장합니다.
- 스레드 동기화:
- 여러 스레드가 특정 순서대로 작업을 수행하도록 제어합니다.
- 제한된 자원 관리:
- 예를 들어, 네트워크 연결, 데이터베이스 연결과 같이 제한된 수의 자원을 사용하는 경우, 세마포어를 사용하여 접근 수를 제한할 수 있습니다.
예
- 데이터베이스 커넥션 풀 관리
- 생산자-소비자 문제
- 네트워크 리소스 관리 (동시에 처리 가능한 연결 제한)
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore semaphore = new Semaphore(2); // 동시에 2개 허용
public void accessResource() {
try {
semaphore.acquire(); // P 연산
System.out.println(Thread.currentThread().getName() + " accessing resource.");
Thread.sleep(1000); // 작업 수행
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release(); // V 연산
System.out.println(Thread.currentThread().getName() + " released resource.");
}
}
////
public static void main(String[] args) {
SemaphoreExample example = new SemaphoreExample();
Runnable task = example::accessResource;
for (int i = 0; i < 5; i++) {
new Thread(task).start(); // 스레드 5개 실행
}
}
}
/////////
Thread-0 accessing resource.
Thread-1 accessing resource.
Thread-0 released resource.
Thread-2 accessing resource.
Thread-1 released resource.
Thread-3 accessing resource.
Thread-2 released resource.
Thread-4 accessing resource.
Thread-3 released resource.
Thread-4 released resource.
- 초기 상태에서 Semaphore의 카운터는 2
- 5개의 스레드가 실행되며, 다음과 같이 동작
- Thread-0와 Thread-1이 먼저 자원을 획득하여 작업을 수행
- 다른 스레드(Thread-2, Thread-3, Thread-4)는 자원이 반환될 때까지 대기
- 작업이 끝난 스레드가 자원을 반환(release())하면, 대기 중인 스레드 중 하나가 자원을 획득
- 이 과정이 반복되며, 모든 스레드가 차례로 작업을 완료
뮤텍스와 바이너리 세마포어는 같은 것? NO
- 뮤텍스는 락을 가진 자만 해제 가능, 세마포어는 그렇지 않다.
- 뮤텍스는 priority inheritance 속성을 가지나 세마포어는 그렇지 않다(누가 시그널을 날릴지 모름)
- 우선순위 높은 작업이 락에 의해 블라킹되면 그 블라킹 작업의 우선순위를 높여 우선순위작업이 빨리 처리되게끔 하는 것
상호배제(단일 자원에 대한 독점적 접근 보장)만 필요하다면 뮤텍스를, 작업 간의 실행순서 동기화가 필요하면 세마포어 사용
언제 뮤텍스를 사용할까?
- 자원에 대한 단일 접근만 보장하면 되는 경우.
- 예:
- 공유 변수나 데이터 구조 보호.
- 파일 쓰기 작업.
언제 세마포어를 사용할까?
- 작업 간 실행 순서를 동기화해야 하는 경우.
- 여러 스레드가 동시에 제한된 자원(예: DB 연결, 네트워크 포트)에 접근할 때.
- 예:
- 생산자-소비자 문제.
- 연결 풀 관리(최대 N개 동시 연결).
- 디비풀; 커넥션풀
728x90
반응형
'개발 > java' 카테고리의 다른 글
[병렬처리] Folkjoinpool & executorService (0) | 2024.11.26 |
---|---|
DB로 분산락 구현 (2) | 2024.11.21 |
[test] org.mockito.exceptions.misusing.PotentialStubbingProblem (1) | 2024.11.15 |
자바와 스프링에서 thread pool (0) | 2024.11.11 |
[test] object mother (2) | 2024.09.26 |