티스토리 뷰
세마포어(Semaphore), 배리어(Barrier), 래치(Latch)란?
멀티스레드 또는 분산 시스템에서 동기화(Synchronization) 및 동시성 제어(Concurrency Control)를 위해 사용하는 주요 개념입니다.
- 세마포어(Semaphore): 공유 자원의 접근을 제한하는 메커니즘
- 배리어(Barrier): 특정 지점까지 모든 스레드가 도착해야 다음 단계로 진행할 수 있도록 하는 동기화 도구
- 래치(Latch): 특정 조건이 충족될 때까지 스레드를 차단하는 동기화 메커니즘
이제 각 개념을 자세히 살펴보겠습니다.
1. 세마포어(Semaphore)
세마포어는 공유 자원(예: 파일, 데이터베이스, 메모리 등)에 대한 접근을 제한하는 동기화 기법입니다.
일반적으로 카운터(counter) 값을 기반으로 동작하며, 이를 통해 몇 개의 스레드가 동시에 자원에 접근할 수 있는지 결정합니다.
세마포어의 종류
- 이진 세마포어(Binary Semaphore, Mutex와 유사)
- 값이 0 또는 1만 가질 수 있는 세마포어
- 한 번에 하나의 스레드만 자원에 접근 가능
- 뮤텍스(Mutex)와 유사하지만, 소유권 개념이 없음
- 카운팅 세마포어(Counting Semaphore)
- 임의의 정수 값을 가질 수 있으며, 특정 개수만큼의 스레드가 동시에 접근 가능
- ex) DB Connection Pool의 최대 연결 개수 제한
세마포어의 동작
세마포어는 두 가지 연산을 수행합니다.
- wait() (P 연산, acquire)
- 세마포어 값을 감소시키고, 값이 0보다 작아지면 대기 상태로 진입
- signal() (V 연산, release)
- 세마포어 값을 증가시키고, 대기 중인 스레드가 있으면 깨움
세마포어 예제 (Java)
import java.util.concurrent.Semaphore;
class SharedResource {
private final Semaphore semaphore = new Semaphore(2); // 최대 2개 스레드 접근 가능
public void accessResource() {
try {
semaphore.acquire(); // wait() 연산
System.out.println(Thread.currentThread().getName() + "가 자원에 접근 중...");
Thread.sleep(2000); // 작업 수행
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release(); // signal() 연산
System.out.println(Thread.currentThread().getName() + "가 자원 사용 종료.");
}
}
}
public class SemaphoreExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
for (int i = 0; i < 5; i++) {
new Thread(resource::accessResource).start();
}
}
}
- Semaphore(2) → 최대 2개의 스레드가 동시에 접근 가능
- 추가적인 스레드는 대기하다가 release()가 호출되면 실행됨
2. 배리어(Barrier)
배리어는 모든 스레드가 특정 지점에 도달해야만 다음 단계로 진행할 수 있도록 하는 동기화 메커니즘입니다.
배리어의 특징
- 모든 스레드가 특정 "점(barrier)"에 도달해야 다음 단계로 진행 가능
- 멀티스레드 병렬 연산에서 작업이 동기적으로 실행되도록 보장
- 일반적으로 병렬 컴퓨팅 및 분산 시스템에서 데이터 동기화 및 병렬 계산을 조율할 때 사용
배리어의 동작
- await() 호출 시, 모든 스레드가 도착할 때까지 대기
- 모든 스레드가 await()을 호출하면 동시에 다음 단계로 진행됨
배리어 예제 (Java - CyclicBarrier)
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
class Worker extends Thread {
private CyclicBarrier barrier;
public Worker(CyclicBarrier barrier) {
this.barrier = barrier;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 작업 수행 중...");
Thread.sleep((long) (Math.random() * 3000));
System.out.println(Thread.currentThread().getName() + " 배리어 도착");
barrier.await(); // 모든 스레드가 도착할 때까지 대기
System.out.println(Thread.currentThread().getName() + " 다음 작업 시작");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public class BarrierExample {
public static void main(String[] args) {
final int threadCount = 3;
CyclicBarrier barrier = new CyclicBarrier(threadCount, () -> System.out.println("모든 스레드가 도착! 다음 단계 실행"));
for (int i = 0; i < threadCount; i++) {
new Worker(barrier).start();
}
}
}
- CyclicBarrier(3) → 3개의 스레드가 도착해야 다음 단계로 진행됨
- 모든 스레드가 await()을 호출하면 다음 단계 실행
3. 래치(Latch)
래치는 특정 조건이 충족될 때까지 스레드가 대기하는 동기화 메커니즘입니다.
래치의 특징
- 특정 이벤트가 발생할 때까지 스레드를 블로킹
- countDown()을 호출하여 래치를 감소시키고, 값이 0이 되면 대기 중인 스레드가 모두 실행됨
- 초기 설정한 값만큼 이벤트가 발생해야 스레드가 해제됨
- 일반적으로 애플리케이션 초기화 및 비동기 작업 완료를 대기할 때 사용
래치 예제 (Java - CountDownLatch)
import java.util.concurrent.CountDownLatch;
class Task extends Thread {
private CountDownLatch latch;
public Task(CountDownLatch latch) {
this.latch = latch;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " 작업 수행 중...");
Thread.sleep((long) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 래치 값 감소
System.out.println(Thread.currentThread().getName() + " 작업 완료. 남은 래치: " + latch.getCount());
}
}
}
public class LatchExample {
public static void main(String[] args) throws InterruptedException {
final int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Task(latch).start();
}
latch.await(); // 모든 스레드가 완료될 때까지 대기
System.out.println("모든 작업이 완료됨! 메인 스레드 실행");
}
}
- CountDownLatch(3) → 3개의 스레드가 완료될 때까지 await()에서 대기
- 각 스레드가 작업을 마칠 때마다 countDown() 호출 → 값이 0이 되면 await()이 해제됨
정리
개념 설명 사용 예시
세마포어(Semaphore) | 공유 자원의 접근을 제한하는 동기화 메커니즘 | 데이터베이스 연결 풀, 쓰레드 동시 실행 제한 |
배리어(Barrier) | 모든 스레드가 특정 지점에 도달해야 다음 단계 진행 가능 | 병렬 계산 동기화, 분산 작업 조율 |
래치(Latch) | 특정 조건이 충족될 때까지 스레드 차단 | 애플리케이션 초기화, 비동기 작업 완료 대기 |
각 개념은 동시성 제어 및 시스템 안정성을 확보하는 데 중요한 역할을 합니다.