여러 자료들을 참고하며 이해한대로 정리한 글이라 틀린 내용이 있을 수도 있습니다. 비판과 지적 환영합니다.
Mutex와 Semaphore의 가장 잘 알려진 차이점은 접근 가능한 스레드/프로세스 수로, n개의 스레드가 하나의 자원에 접근할 수 있는 방식이 Semaphore며 binary semaphore가 곧 mutex라고 생각했다. 대표적인 차이점이긴 하지만 찾아보니 뭔가 더 있어서 흥미로워서 정리해본다.
"상호 배제"라는 맥락에서만 보면 이 말은 어폐가 있다.
아니, 애초에 스레드 간 race condition을 피하기 위해 하나의 스레드만 critical section에 진입할 수 있도록 한다며? 그러면 Semaphore는 대체 뭐지?
우선 공유 자원 != 임계 영역이라는 걸 짚고 넘어가야 할 것 같다.
ㄴ예. 사실 제가 했던 오해^^
쉬운 코드 님의 예시에 따르면, 애초에 양변기가 3개라(=공유 자원) 3명까지 수용 가능한 상황이 있을 수 있다.
공식 문서를 참고해보니 친절하게 어떤 상황에서 세마포어를 사용하는지 예시가 나와 있었다.
Thread pool, Connection pool 등 Computer Science에서 제네릭 하게 활용되는 개념인 Pool이 있다. 보통은 객체를 생성하는 비용을 줄이기 위해 n개의 객체를 미리 생성해둬서 재사용할 수 있도록 활용된다.
알고보니 Java에서 이 Pool을 구현하기 위해 Semaphore를 활용했다고 한다.
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
// ...
}
여러개의 스레드가 자원(=여러개의 객체가 있는 풀)에 접근할 수 있도록 하는 대표적인 예시라고 할 수 있다.
Mutex는 lock을 선점한 스레드만 unlock을 할 수 있는 반면, Semaphore는 lock의 ownership 개념이 없다.
즉, 세마포어의 경우 A와 B 두 스레드가 있다면 누가 unlock할 지 모르는 일인 반면, Mutex는 lock을 선점한 스레드 A가 unlock을 해야 한다.
...
the binary semaphore has the property (unlike many Lock implementations), that the "lock" can be released by a thread other than the owner (as semaphores have no notion of ownership)
👉 참고 : https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/Semaphore.html
Mutex는 상호 배제에 충실하다면, Semaphore는 순서를 보장하는 기능까지 할 수 있다.
쉬운 코드 님의 예시를 차용 했습니다
task1 수행
semaphore -> signal() // lock 해제
task2 수행
semaphore -> wait(); // lock 걸기
task3 수행
이 경우 task1, 2, 3의 실행 순서가 어떻게 될까?
한가지 확실한 건, task3는 반드시 task1의 작업이 끝난 뒤에 실행된다.
thread A에 의해서 task1이 먼저 실행되건, thread B에 의해 task2가 먼저 실행되건, lock이 풀린 뒤 task3이 실행될 수 있기 때문이다.
그러므로 작업 간의 실행 순서 동기화가 필요하다면 Semaphore를 쓰는 게 적절할 것이다.