세마포어와 뮤텍스는 공유자원 접근을 제어해 Race Condition과 Deadlock을 막는다

운영체제나 동시성 관련 내용을 공부하다 보면 꼭 나오는 개념이 뮤텍스와 세마포어다. 처음엔 비슷해 보여서 헷갈리는데, 용도를 이해하면 구분이 명확해진다.

왜 이게 필요한가

멀티스레드 환경에서는 여러 스레드가 동시에 같은 자원(파일, 메모리, DB 연결 등)에 접근할 수 있다. 아무런 제어 없이 접근하면 세 가지 문제가 생긴다.

  • Race Condition: 두 스레드가 동시에 값을 읽고 수정해서 잘못된 결과가 저장된다. 은행 잔액을 두 군데서 동시에 수정하는 상황이 대표적이다.
  • Deadlock: A가 B의 자원을 기다리고, B가 A의 자원을 기다리는 상태. 서로 양보하지 않아 프로세스 전체가 멈춰버린다.
  • Starvation: 우선순위가 낮은 스레드가 계속 자원을 받지 못하고 대기만 하는 상태.

이 문제들을 해결하기 위해 자원에 대한 접근을 제어하는 도구가 뮤텍스와 세마포어다.

뮤텍스 (Mutex)

뮤텍스는 이진 잠금이다. 열쇠가 하나뿐인 화장실처럼, 한 번에 하나의 스레드만 임계 영역에 들어갈 수 있다. 잠금/해제 두 가지 상태만 존재하고, 잠금을 건 스레드만 해제할 수 있다는 점이 특징이다.

대기 중인 스레드가 CPU를 계속 점유하는 바쁜 대기(busy waiting) 문제가 있다. 임계 영역이 매우 짧으면 컨텍스트 스위칭 비용보다 낫지만, 길어지면 CPU를 낭비하게 된다.

세마포어 (Semaphore)

세마포어는 카운팅 잠금이다. 주차장에서 남은 자리 수를 세는 것과 비슷하다. N개의 스레드까지 동시에 자원을 사용할 수 있도록 허용한다.

  • Binary Semaphore: 값이 0과 1뿐. 뮤텍스와 비슷하게 동작한다.
  • Counting Semaphore: 설정한 숫자만큼 동시 접근을 허용한다.

뮤텍스와 달리 잠금을 건 것과 해제하는 것이 다른 스레드일 수 있다. 이 특성 때문에 생산자-소비자 패턴 같은 스레드 간 신호 전달에 자주 쓰인다.

실제 사용 예시

DB 트랜잭션: MySQL 같은 DBMS의 잠금 메커니즘이 이 개념을 직접 구현한다. 여러 트랜잭션이 동시에 같은 행을 수정하지 못하게 제어한다.

웹 서버: Nginx는 파일을 동시에 쓸 때 뮤텍스 락을 사용해 충돌을 방지한다.

프린터 스풀러: 여러 사용자가 동시에 인쇄 요청을 하면 세마포어로 순서를 관리한다.

Java에서는 synchronized 키워드와 ReentrantLock, Semaphore 클래스로 이 개념을 직접 활용할 수 있다.

실전에서 기억할 것

직접 동기화 로직을 구현하기보다는 검증된 라이브러리나 언어 기본 메커니즘을 쓰는 게 안전하다. 임계 영역은 가능한 짧게 유지하고, 중첩 잠금은 Deadlock의 씨앗이니 특히 조심해야 한다.

연결 (이유)

출처(참고문헌)

https://jwprogramming.tistory.com/13