세마포어
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
세마포어는 컴퓨터 과학에서 공유 자원에 대한 접근을 제어하기 위한 동기화 메커니즘이다. 네덜란드어에서 유래된 P와 V 연산을 통해 세마포어의 값을 조작하며, P는 자원 획득을, V는 자원 반환을 의미한다. 세마포어는 정수 값을 가지는 변수로, P와 V 연산에 의해서만 접근할 수 있으며, 계수 세마포어와 이진 세마포어로 구분된다. 세마포어는 임계 구역 진입 전 P 연산, 임계 구역 종료 후 V 연산을 수행하며, wait (P)와 signal (V) 연산을 통해 자원 접근을 관리한다. 바쁜 대기, 재움 큐 등의 구현 방식을 가지며, 생산자-소비자 문제, 배턴 넘겨주기 패턴 등에서 활용된다. 하지만 잘못 사용 시 교착 상태, 상호 배제 실패 등의 약점을 가지며, 뮤텍스와 비교하여 사용 방식에 차이가 있다. POSIX, Windows, .NET, Java 등 다양한 플랫폼과 언어에서 세마포어 관련 기능을 제공한다.
더 읽어볼만한 페이지
- 동기화 - 동기화 (컴퓨터 과학)
동기화는 컴퓨터 과학에서 다중 프로세스 또는 스레드가 공유 자원에 접근할 때 발생하는 문제를 해결하기 위한 기술이며, 경쟁 조건 방지 및 데이터 일관성 유지를 위해 세마포어, 뮤텍스 등의 메커니즘을 통해 구현된다. - 동기화 - 지터
지터는 디지털 신호의 위상 흔들림으로 데이터 오류를 유발하며, 랜덤 지터와 디터미니스틱 지터로 나뉘고, 컴퓨터 네트워크에서는 패킷 지연 변동을 의미하며, 지터 완화 기술로 제어된다. - 병행성 - 기아 상태
기아 상태는 컴퓨터 과학에서 프로세스가 필요한 자원을 할당받지 못해 무한정 대기하는 현상으로, 단순한 스케줄링 알고리즘, 우선순위 역전, 교착 상태 등으로 인해 발생하며 시스템 효율성을 저하시키고 작업 완료를 지연시키지만, 에이징 기법과 같은 공정한 스케줄링 알고리즘이나 우선순위 조정으로 해결할 수 있다. - 병행성 - 동기화 (컴퓨터 과학)
동기화는 컴퓨터 과학에서 다중 프로세스 또는 스레드가 공유 자원에 접근할 때 발생하는 문제를 해결하기 위한 기술이며, 경쟁 조건 방지 및 데이터 일관성 유지를 위해 세마포어, 뮤텍스 등의 메커니즘을 통해 구현된다. - 컴퓨터 매개 통신 - 유즈넷
유즈넷은 1979년 구상된 분산 네트워크 기반 토론 시스템으로, 인터넷 커뮤니티의 효시로서 중요한 프로젝트의 시작에 기여했으나, 스팸 문제와 소셜 미디어 등장 등으로 쇠퇴하여 2024년 구글 그룹스가 지원을 중단하며 정보 접근성에 변화가 생겼다. - 컴퓨터 매개 통신 - 전자 게시판
전자 게시판은 컴퓨터 통신망을 통해 사용자들이 정보를 교환하고 공유하는 시스템으로, PC 통신에서 시작하여 온라인 커뮤니티로 발전하며 다양한 유형과 독특한 문화를 형성해 왔다.
세마포어 | |
---|---|
자료 구조 | |
종류 | 추상적 자료형 |
분류 | 동기화 도구 |
프로그래밍 | |
최초 고안자 | 에츠허르 W. 데이크스트라 |
고안 시기 | 1962년 ~ 1963년 |
목적 | 상호 배제 구현, 프로세스 동기화 |
사용 분야 | 운영 체제, 병행 프로그래밍 |
구현 방식 | 정수 변수, 원자적 연산 |
연산 | wait (P), signal (V) |
특징 | |
장점 | 간단한 구현 교착 상태 및 기아 상태 방지 가능 |
단점 | 우선 순위 역전 문제 발생 가능 데이크스트라 알고리즘에서의 세마포어 사용 제한 복잡한 로직 구현 시 오류 발생 가능성 증가 |
대안 | 뮤텍스 모니터 조건 변수 |
2. 역사
V와 P라는 정식 명칭은 네덜란드어 단어의 머리글자에서 유래되었다. V는 일반적으로 "증가"를 의미하는 ''verhogen''으로 설명된다. P에 대해서는 "시험하다" 또는 "시도하다"를 의미하는 ''proberen'',[6] "통과하다"를 의미하는 ''passeren'', "잡다"를 의미하는 ''pakken'' 등 여러 설명이 제시되었다. 다익스트라의 초기 논문[1]에서는 ''P''의 의미를 ''passering''("통과")으로, V의 의미를 ''vrijgave''("해제")로 제시하고 있다. 또한 이 용어는 철도 신호에 사용되는 용어에서 가져온 것이라고 언급하고 있다. 다익스트라는 이후 ''P''가 "감소하려고 시도하다"를 의미하는 ''probeer te verlagen''의 줄임말인 ''prolaag''를 의미한다고 밝혔으며, 다른 경우에 사용된 용어와 유사하게 "감소시키려고 시도하다"를 의미한다.[8][9][10]
세마포어 S는 정수 값을 가지는 변수이며, P와 V라는 명령에 의해서만 접근할 수 있다. P와 V는 각각 try와 increment를 뜻하는 네덜란드어 Proberennl과 Verhogennl의 머릿글자를 딴 것이다.[1]
ALGOL 68, 리눅스 커널,[11] 및 일부 영어 교과서에서는 ''V''와 ''P'' 연산을 각각 ''up''과 ''down''이라고 부른다. 소프트웨어 공학 실무에서는 종종 ''signal''과 ''wait'',[12] ''release''와 ''acquire'' (표준 자바 라이브러리),[13] 또는 ''post''와 ''pend''라고 부른다. 일부 텍스트에서는 원래의 네덜란드어 머리글자에 맞춰 ''vacate''와 ''procure''라고 부르기도 한다.[14][15]
세마포어의 개념은 네덜란드 컴퓨터 과학자 에츠허르 데이크스트라가 고안했다.[16] 현재 다양한 운영 체제에서 채용되고 있다.
"en:semaphore"의 원래 의미는 "시각에 의한 통신·신호" 전반을 가리키며, 전신 또는 거기에서 파생된 철도의 암호 신호기 (및 자동차의 방향 지시등), 수기 신호 등이 포함된다. 역사적으로 사용된 이름인 '''P'''와 '''V'''는 네덜란드어 단어의 머리글자에서 유래했다. '''V'''는 ''verhogen'' (증가하다)를 의미한다. '''P'''에 대해서는, ''proberen'' (테스트하다)[18], ''passeer'' (통과), ''probeer'' (시도하다), ''pakken'' (잡다) 등 여러 설명이 있다. 세마포어의 본래 의미인 세마포 통신에서, '''V'''를 ''verhoog'' (팔을 올리다, 통행 금지), '''P'''를 ''passeren'' (팔을 내리다, 통행 허가)로 보는 설도 있다. 그러나 다익스트라 자신은 '''P'''를 ''probeer te verlagen'' (감소시키려는 시도)를 줄인 혼성어 ''prolaag''의 머리글자라고 했다.[19][20][21][22]
ALGOL 68이나 리눅스 커널[23], 몇몇 영어 교과서에서는 '''P'''와 '''V'''를 각각 ''down''과 ''up''으로 사용한다. 소프트웨어 공학에서는 일반적으로 '''wait'''과 '''signal''', '''acquire'''와 '''release''', '''pend'''와 '''post''' 등으로 불리는 경우가 많다. 교과서에 따라 원래의 네덜란드어 머리글자에 맞춰 '''procure'''와 '''vacate'''로 사용하기도 한다.
3. 구성
P는 임계 구역에 들어가기 전에 수행되고, V는 임계 구역에서 나올 때 수행된다. 이때 변수 값을 수정하는 연산은 모두 원자성을 만족해야 한다. 다시 말해, 한 프로세스(또는 스레드)에서 세마포어 값을 변경하는 동안 다른 프로세스가 동시에 이 값을 변경해서는 안 된다.
V와 P라는 정식 명칭은 네덜란드어 단어의 머리글자에서 유래되었다. V는 일반적으로 "증가"를 의미하는 ''verhogen''으로 설명된다. P에 대해서는 여러 설명이 제시되었는데, "시험하다" 또는 "시도하다"를 의미하는 ''proberen'',[6] "통과하다"를 의미하는 ''passeren'', "잡다"를 의미하는 ''pakken'' 등이 있다. 다익스트라의 초기 논문에서는 ''P''의 의미를 ''passering''("통과")으로, V의 의미를 ''vrijgave''("해제")로 제시하고 있다. 또한 이 용어는 철도 신호에 사용되는 용어에서 가져온 것이라고 언급하고 있다. 다익스트라는 이후 ''P''가 "감소하려고 시도하다"를 의미하는 ''probeer te verlagen''의 줄임말인 ''prolaag''를 의미한다고 밝혔으며, 다른 경우에 사용된 용어와 유사하게 "감소시키려고 시도하다"를 의미한다.[8][9][10]
ALGOL 68, 리눅스 커널,[11] 및 일부 영어 교과서에서는 ''V''와 ''P'' 연산을 각각 ''up''과 ''down''이라고 부른다. 소프트웨어 공학 실무에서는 종종 ''signal''과 ''wait'',[12] ''release''와 ''acquire'' (표준 자바 라이브러리),[13] 또는 ''post''와 ''pend''라고 부른다. 일부 텍스트에서는 원래의 네덜란드어 머리글자에 맞춰 ''vacate''와 ''procure''라고 부르기도 한다.[14][15]
세마포어는 어떤 자원이 몇 개 사용 가능한지를 나타내는 기록이라고 생각하면 이해하기 쉽다. 그리고 그 자원을 사용하거나 해제할 때 그 기록을 "안전하게"(즉, 경합 상태가 발생하지 않도록) 수정하고, 필요에 따라 자원을 사용할 수 있을 때까지 대기하는 연산이 결합되어 있다.
카운팅 세마포어에서의 두 가지 연산은 역사적으로 '''V''' (또는 `signal()`)과 '''P''' (또는 `wait()`)로 표기된다. '''V''' 연산은 세마포어 '''S'''를 증가시키고, '''P''' 연산은 감소시킨다. 대괄호로 묶인 부분은 원자적 연산임을 의미하며, 그 부분의 중간 과정은 다른 실행 단위에서는 보이지 않는다.
세마포어 '''S'''의 값은 그 시점에서 사용 가능한 자원의 개수이다. '''P''' 연산은 자원을 획득하려는 것이며, 세마포어로 보호되고 있는 자원이 사용 가능하게 될 때까지 비지 웨이트 또는 슬립으로 기다리게 된다. '''V''' 연산은 그 반대로, 사용하고 있던 자원을 해제한다.
`wait()`와 `signal()`을 간단히 설명하면 다음과 같다.
많은 OS는 효율적인 세마포어의 프리미티브를 제공하고 있으며, 세마포어를 증가시켰을 때 한 개의 대기 상태의 실행 단위만을 언블록한다. 즉, 여러 실행 단위를 동시에 언블록했을 때의 불필요한 세마포어 값의 확인 처리를 막고 있다.
카운팅 세마포어의 개념은 한 번에 여러 개의 자원을 획득/해제할 수 있도록 확장할 수 있으며, 유닉스에서의 구현도 그와 같다. 이 경우의 '''V''' 및 '''P''' 연산은 다음과 같이 수정된다.
'''function''' V(semaphore S, integer I):
\[S ← S + I]
'''function''' P(semaphore S, integer I):
'''repeat:'''
\['''if''' S >= I:
S ← S - I
'''break''']
자원 기아를 방지하기 위해, 세마포어에는 실행 단위의 큐 (보통은 FIFO)가 하나 부속되어 있다. 세마포어 값이 0일 때 '''P''' 연산을 하면, 그 실행 단위는 세마포어 부속 큐에 추가된다. 다른 실행 단위가 '''V''' 연산으로 세마포어 값을 증가시켰을 때, 큐상에 실행 단위가 있으면, 그 중 하나를 큐에서 빼내어 실행을 재개시킨다. 실행 단위에 우선 순위가 설정되어 있는 경우, 큐상에서 우선 순위 순으로 실행 단위를 정렬하는 등, 가장 우선 순위가 높은 실행 단위가 처음에 실행을 재개할 수 있도록 한다.
구현에 있어서 증가/감소 및 비교의 원자성이 보장되지 않는 경우, 증가나 감소가 행해지지 않거나, 세마포어 값이 부정확해질 위험이 생긴다. 원자성을 달성하려면, 읽기-수정-쓰기를 원자적으로 실행할 수 있는 기계어 명령어를 사용한다. 그러한 기계어 명령어가 없는 경우, 데커의 알고리즘 등의 소프트웨어에 의한 상호 배제를 사용한다. 싱글 프로세서 시스템에서의 원자적 연산은, 선점을 일시적으로 금지하거나, 인터럽트를 마스크하는 것으로도 실현할 수 있다. 멀티프로세서 시스템에서는 그것만으로는 불충분하며, 같은 세마포어를 공유하는 두 개의 프로그램이 별도의 프로세서 상에서 동시에 동작하고 있는 경우에 대처할 수 없다. 따라서 테스트 앤드 세트 명령 등을 사용하여 세마포어 변수에 대한 접근을 락해야 한다.
4. 종류
세마포어는 크게 카운팅 세마포어와 이진 세마포어로 나뉜다.
- 카운팅 세마포어(counting semaphore): 사용 가능한 자원의 수를 나타내는 세마포어이다. 초기값은 사용 가능한 자원의 총개수로 설정되며, 세마포어 값의 범위에는 제한이 없다. 임의 개수의 자원을 다룰 때 사용된다.
- 이진 세마포어(binary semaphore): 0 또는 1의 값만 가질 수 있는 세마포어이다. '잠금/해제' 또는 '사용 가능/사용 불가'와 같이 두 가지 상태만을 나타낼 때 사용된다. 뮤텍스와 동일한 기능을 수행하며, 카운팅 세마포어보다 간단하게 구현할 수 있다. 하드웨어의 지원을 받는 Test and Set 등의 기능을 이용하여 구현하기도 한다. 또한 이진 세마포어를 이용하여 카운팅 세마포어를 구현할 수도 있다.
세마포어는 어떤 자원이 몇 개 사용 가능한지를 나타내는 기록이라고 할 수 있다. 자원을 사용하거나 해제할 때 이 기록을 경합 상태가 발생하지 않도록 안전하게 수정하고, 필요에 따라 자원을 사용할 수 있을 때까지 대기하는 연산이 결합되어 있다. 세마포어는 경합 상태를 방지하는 데 유용하지만, 세마포어를 사용한다고 해서 프로그램에서 경합 상태가 완전히 사라지는 것은 아니다.[16]
5. 구현 방법
세마포어의 구현 방법에는 크게 바쁜 대기와 재움 큐를 사용하는 두 가지 방식이 있다.
최초에 제시된 바쁜 대기를 이용한 방법은 다음과 같다.
P(S) {
while S <=0; // 아무것도 하지 않음 (반복문)
S--;
}
V(S) {
S++;
}
이 방법은 프로세스가 임계 구역에 진입할 수 있을 때까지 빈 반복문을 수행하며 대기한다.[1]
바쁜 대기 방식의 단점을 보완하기 위해 재움 큐를 활용하는 방법은 다음과 같다.
P(S) {
S--;
if S < 0
// 이 프로세스를 재움 큐에 추가 (잠 듦)
}
V(S) {
S++;
if S <= 0
// 재움 큐로부터 프로세스를 제거 (깨어남)
}
이 방법은 세마포어 값이 0 이하이면 프로세스를 재움 큐에 넣어 대기시킨다.
세마포어는 일반적으로 P와 V 연산으로 조작된다. 세마포어 S의 값은 사용 가능한 자원의 수를 나타낸다. P 연산은 자원을 획득하는 연산으로, 세마포어 값이 0보다 클 때까지 대기한다. V 연산은 자원을 반납하는 연산이다.
세마포어 변수의 값은 P와 V 연산을 통해서만 변경할 수 있다.
`wait` (P)와 `signal` (V) 연산은 다음과 같이 동작한다.
- `wait`: 세마포어 값을 1 감소시킨다. 값이 음수가 되면 프로세스는 블록되어 세마포어의 큐에 추가된다.
- `signal`: 세마포어 값을 1 증가시킨다. 증가 전 값이 음수였다면, 대기 큐에서 프로세스를 꺼내 준비 큐로 보낸다.
유닉스에서는 여러 개의 자원을 한 번에 획득/해제할 수 있는 카운팅 세마포어 개념이 구현되어 있다. 수정된 V 및 P 연산은 다음과 같다.
'''function''' V(semaphore S, integer I):
[S ← S + I]
'''function''' P(semaphore S, integer I):
'''repeat:'''
\['''if''' S ≥ I:
S ← S - I
'''break''']
자원 기아를 방지하기 위해, 세마포어에는 일반적으로 FIFO 방식으로 동작하는 프로세스 큐가 연결된다.
구현 시에는 증가, 감소, 비교 연산의 원자성을 보장해야 한다. 단일 프로세서 환경에서는 선점을 일시 중단하거나 인터럽트를 비활성화하여 원자성을 확보할 수 있다. 멀티프로세서 환경에서는 테스트 앤드 세트 같은 명령어를 사용하여 세마포어 변수에 대한 접근을 락 해야 한다.
5. 1. 바쁜 대기 (Busy Waiting)
최초로 제시된 방법은 바쁜 대기를 이용한 방법이다.[1] 이 방법은 임계 구역에 들어갈 수 있을 때까지 빈 반복문을 수행하므로, 단일처리기 다중프로세스 환경에서 처리기 효율이 떨어진다. 또한 대기 중인 프로세스들 중 어느 것을 먼저 임계 구역에 진입시킬지를 결정할 수 없다.세마포어 ''S''의 값은 현재 사용 가능한 자원의 단위 수를 나타낸다. P 연산은 세마포어에 의해 보호되는 자원이 사용 가능해질 때까지 시간을 낭비하거나 대기하며, 자원이 즉시 할당된다.
자원 기아를 방지하기 위해, 세마포어는 프로세스의 관련 큐를 갖는다(일반적으로 FIFO). 프로세스가 값이 0인 세마포어에 대해 P 연산을 수행하면, 해당 프로세스는 세마포어의 큐에 추가되고 실행이 일시 중단된다. 다른 프로세스가 V 연산을 수행하여 세마포어를 증가시키고, 큐에 프로세스가 있는 경우, 그중 하나가 큐에서 제거되고 실행을 재개한다. 프로세스에 서로 다른 우선 순위가 있는 경우, 큐는 정렬될 수 있으며, 가장 높은 우선 순위 프로세스가 먼저 큐에서 가져간다.
5. 2. 재움 큐 (Sleep Queue)
최초 방법의 단점을 보완한 방법으로, 재움 큐를 활용하여 프로세스를 재우는 방식이다.세마포어 ''S''의 값은 현재 사용 가능한 자원의 단위 수를 나타낸다. P 연산은 세마포어에 의해 보호되는 자원이 사용 가능해질 때까지 대기하며, 자원이 즉시 할당된다. V 연산은 그 반대로, 프로세스가 자원 사용을 완료한 후 다시 자원을 사용 가능하게 만든다.
`wait` (P)와 `signal` (V) 연산을 이해하는 간단한 방법은 다음과 같다.
- `wait`: 세마포어 변수의 값을 1 감소시킨다. 세마포어 변수의 새 값이 음수이면, `wait`를 실행하는 프로세스는 블록된다(즉, 세마포어의 큐에 추가된다). 그렇지 않으면, 프로세스는 실행을 계속하여 자원의 한 단위를 사용한다.
- `signal`: 세마포어 변수의 값을 1 증가시킨다. 증가 후, 증가 전 값이 음수였다면 (자원을 기다리는 프로세스가 있다는 의미), 블록된 프로세스를 세마포어의 대기 큐에서 준비 큐로 전송한다.
자원 기아를 방지하기 위해, 세마포어는 프로세스의 관련 큐를 갖는다(일반적으로 FIFO 의미 체계 사용). 프로세스가 값이 0인 세마포어에 대해 P 연산을 수행하면, 해당 프로세스는 세마포어의 큐에 추가되고 실행이 일시 중단된다. 다른 프로세스가 V 연산을 수행하여 세마포어를 증가시키고, 큐에 프로세스가 있는 경우, 그중 하나가 큐에서 제거되고 실행을 재개한다.
6. 적용 사례
세마포어는 다양한 프로그래밍 환경에서 활용될 수 있다.
변수 ''A''와 불리언 변수 ''S''를 생각해보자. ''A''는 ''S''가 참일 때만 접근 가능하다. 즉, ''S''는 ''A''에 대한 세마포어 역할을 한다.
기차역 바로 앞에 있는 신호등(''S'')을 예로 들 수 있다. 신호가 녹색이면 기차역(''A'')에 진입할 수 있지만, 노란색이나 빨간색이면 접근할 수 없다.
10명의 사용자만 지원하는 시스템(S=10)을 가정해 보자. 사용자가 로그인하면 세마포어 ''S''는 1 감소하고, 로그아웃하면 ''S''는 1 증가하여 사용 가능한 로그인 슬롯을 나타낸다. ''S''가 0이면 로그인을 원하는 사용자는 ''S''가 증가할 때까지 기다려야 한다. 로그인 요청은 FIFO 큐에 대기열로 추가되고, ''S''가 증가하면 큐에서 제거되어 로그인이 허용된다.
6. 1. 도서관 열람실 비유
도서관에 한 번에 한 명의 학생만 사용할 수 있는 열람실이 10개 있다고 가정해 보자. 학생들은 안내 데스크에서 빈 방을 요청해야 한다. 빈 방이 없으면 학생들은 누군가 방을 비울 때까지 데스크에서 기다린다. 학생이 방 사용을 마치면 데스크로 돌아와서 방이 비어 있음을 알려야 한다.가장 간단한 구현에서 사서는 안내 데스크에서 사용 가능한 빈 방의 수만 알고 있다. 학생이 방을 요청하면 사서는 이 숫자를 감소시키고, 방을 반납하면 이 숫자를 증가시킨다. 방은 원하는 만큼 사용할 수 있으므로, 미리 예약하는 것은 불가능하다.
이 시나리오에서 안내 데스크의 카운트 홀더는 카운팅 세마포어를 나타내고, 방은 자원이며, 학생은 프로세스/스레드를 나타낸다. 세마포어의 값은 처음에 10이고, 모든 방이 비어 있다. 학생이 방을 요청하면 접근 권한이 부여되고, 세마포어의 값은 9로 변경된다. 다음 학생이 오면 8, 다음은 7 등으로 떨어진다. 누군가 방을 요청했을 때 세마포어의 현재 값이 0이면,[2] 방이 비워질 때까지 기다려야 한다. 만약 방 중 하나가 반납되었지만, 여러 명의 학생들이 기다리고 있다면, 방을 사용할 사람을 선택하기 위해 FIFO 또는 무작위 선택 등의 방법을 사용할 수 있다.[17]
6. 2. 생산자-소비자 문제
생산자-소비자 문제에서 하나의 프로세스(생산자)는 데이터를 생성하고 다른 프로세스(소비자)는 이를 받아서 사용한다. 이들은 최대 크기 ''N''의 큐를 사용하여 통신하며 다음과 같은 조건을 따른다.[1]- 소비자는 큐가 비어 있으면 생산자가 무언가를 생성할 때까지 기다려야 한다.
- 생산자는 큐가 가득 차면 소비자가 무언가를 소비할 때까지 기다려야 한다.
생산자-소비자 문제에 대한 세마포어 해결책은 `emptyCount`와 `fullCount` 두 개의 세마포어를 사용하여 큐의 상태를 추적한다. `emptyCount`는 큐의 빈 공간 개수를 나타내고, `fullCount`는 큐의 요소 개수를 나타낸다. 무결성을 유지하기 위해 `emptyCount`는 큐의 실제 빈 공간 개수보다 낮을 수 있지만, 높을 수는 없으며, `fullCount`는 큐의 실제 항목 개수보다 낮을 수 있지만, 높을 수는 없다. 빈 공간과 항목은 두 종류의 자원(빈 상자와 채워진 상자)을 나타내며, 세마포어 `emptyCount`와 `fullCount`는 이러한 자원을 제어한다.[1]
이진 세마포어 `useQueue`는 큐 자체의 상태 무결성이 손상되지 않도록 보장한다. 예를 들어, 두 생산자가 동시에 빈 큐에 항목을 추가하려 하여 내부 상태가 손상되는 경우를 방지한다. 또는 이진 세마포어 대신 뮤텍스를 사용할 수 있다.[1]
`emptyCount`의 초기값은 ''N''이고, `fullCount`의 초기값은 0이며, `useQueue`의 초기값은 1이다.[1]
생산자는 다음을 반복적으로 수행한다.[1]
'''생산:'''
P(emptyCount)
P(useQueue)
putItemIntoQueue(item)
V(useQueue)
V(fullCount)
소비자는 다음을 반복적으로 수행한다.[1]
'''소비:'''
P(fullCount)
P(useQueue)
item ← getItemFromQueue()
V(useQueue)
V(emptyCount)
다음은 구체적인 예시이다.[1]
# 단일 소비자가 임계 구역에 진입한다. `fullCount`가 0이므로 소비자는 블록된다.
# 여러 생산자가 생산자 임계 구역에 진입한다. `emptyCount`가 진입을 제한하기 때문에 최대 ''N''명의 생산자만 임계 구역에 진입할 수 있다.
# 생산자는 한 번에 하나씩 `useQueue`를 통해 큐에 접근하여 항목을 큐에 넣는다.
# 첫 번째 생산자가 임계 구역을 종료하면 `fullCount`가 증가하여 하나의 소비자가 임계 구역에 진입할 수 있다.
`emptyCount`는 큐의 실제 빈 공간 개수보다 훨씬 낮을 수 있다. 예를 들어, 많은 생산자가 `emptyCount`를 감소시켰지만 빈 공간을 채우기 전에 `useQueue`에서 자신의 차례를 기다리고 있는 경우를 생각할 수 있다. `emptyCount + fullCount ≤ ''N''`은 항상 유지되며, 생산자나 소비자가 임계 구역을 실행하지 않는 경우에만 등식이 성립한다.[1]
6. 3. 배턴 넘겨주기 패턴
"배턴 넘겨주기" 패턴[3][4][5]은 그레고리 R. 앤드루스가 제안한 일반적인 방식으로, 여러 프로세스가 복잡한 접근 조건(특정 우선 순위 기준 충족 또는 기아 방지 등)을 가지고 동일한 자원을 놓고 경쟁하는 많은 복잡한 동시 프로그래밍 문제를 해결하기 위한 것이다. 공유 자원이 주어지면, 이 패턴은 관련된 각 프로세스(또는 프로세스 클래스)에 대해 개인적인 "priv" 세마포어(0으로 초기화)와 단일 상호 배제 "mutex" 세마포어(1로 초기화)가 필요하다.이 패턴은 자원을 해제하는 프로세스뿐만 아니라 새롭게 다시 활성화된 프로세스가 최대 하나의 중단된 프로세스를 활성화, 즉 "배턴을 넘겨주기"를 하기 때문에 "배턴 넘겨주기"라고 불린다. mutex는 프로세스가 스스로를 중단하려 할 때(resource_acquire), 또는 pass_the_baton이 다른 중단된 프로세스를 다시 활성화할 수 없을 때만 해제된다.
7. 약점
세마포어는 P함수와 V함수의 동작이 독립적이므로 잘못 사용하면 문제가 발생할 수 있다. 예를 들어 다음과 같다.
- P - 임계 구역 - P: 현재 프로세스가 임계 구역에서 빠져나올 수 없게 된다. 다른 프로세스들은 임계 구역에 들어갈 수 없으므로 교착 상태가 발생한다.
- V - 임계 구역 - P: 둘 이상의 프로세스가 동시에 임계 구역에 들어갈 수 있으므로 상호 배제를 보장할 수 없다.
고급 언어에서는 동기화를 제공해야 한다. 자원 풀 접근 제어에 사용될 때, 세마포어는 단지 "얼마나 많은" 자원이 비어 있는지 추적한다. 어떤 자원이 비어 있는지는 추적하지 않으므로, 특정 여유 자원을 선택하려면 다른 메커니즘(더 많은 세마포어를 포함)이 필요할 수 있다.
이러한 방식은 세마포어 개수가 여러 다른 동작에 유용한 트리거 역할을 할 수 있어 강력하다. 예를 들어, 사서는 학생이 남아 있지 않을 때 열람실의 불을 끄거나, 대부분의 방이 사용 중일 때 방이 매우 붐빈다는 표지판을 걸 수 있다.
프로토콜이 성공하려면 응용 프로그램이 이를 올바르게 따라야 한다. 단 하나의 프로세스라도 잘못되면 공정성과 안전성이 손상될 수 있다. (프로그램이 느리게 동작하거나, 예측할 수 없게 동작하거나, 멈추거나, 충돌할 수 있다.) 여기에는 다음이 포함된다.
- 자원을 요청하고 해제하는 것을 잊는 경우
- 요청한 적 없는 자원을 해제하는 경우
- 필요 없이 오랫동안 자원을 보유하는 경우
- 먼저 요청하지 않고(또는 해제한 후에) 자원을 사용하는 경우
모든 프로세스가 이러한 규칙을 따르더라도, 서로 다른 세마포어에 의해 관리되는 서로 다른 자원이 있고 프로세스가 한 번에 둘 이상의 자원을 사용해야 하는 경우, 교착 상태가 발생할 수 있다. 철학자 식사 문제가 그 예시이다.
여러 자원에 대해 사용하는 경우, 세마포어는 개별 자원의 사용/해제 상태를 파악하지 않고, 단순히 개수만 유지한다. 특정 자원을 지정하고 싶을 때는 다른 기구가 필요하다(여러 세마포어의 조합으로도 가능하다).
실행 단위군은 해당 프로토콜을 따른다는 점에서 신뢰를 받는다. 하나의 실행 단위가 잘못된 동작을 하면, 공정성과 안전성이 손상되어 성능 저하, 부정 동작, 프리즈, 크래시 등이 발생할 수 있다. 잘못된 동작의 예시는 다음과 같다.
- 자원을 요청해 놓고, 사용 후에 해제하는 것을 잊어버린다.
- 요청한 적 없는 자원을 해제한다.
- 사용하지 않는 자원을 장기간 획득한 채로 둔다.
- 요청하지 않고 자원을 사용한다.
- 해제된 자원임에도 불구하고 해당 자원을 계속 사용한다.
모든 실행 단위가 해당 규칙을 따른다고 하더라도, 자원이 여러 종류 있고, 각각에 세마포어가 설정되어 있는 경우, 실행 단위가 여러 자원을 동시에 사용한다면 새로운 문제가 발생할 수 있다. 식사하는 철학자 문제가 그러한 상황을 보여준다.
8. 뮤텍스와의 비교
뮤텍스는 이진 세마포어와 동일한 기본 구현을 사용하는 경우도 있는 잠금 메커니즘이다. 하지만 사용 방식에서 차이가 있다. 이진 세마포어를 통칭하여 뮤텍스라고 부르기도 하지만, 실제 뮤텍스는 보다 구체적인 사용 사례와 정의를 가지며, 뮤텍스를 잠근 태스크만이 이를 해제해야 한다. 이러한 제약은 세마포어를 사용할 때 발생할 수 있는 몇 가지 잠재적인 문제를 해결하기 위한 것이다.
- 우선순위 역전: 뮤텍스가 누가 잠갔고 누가 해제해야 하는지 알고 있다면, 더 높은 우선순위의 태스크가 뮤텍스를 기다리기 시작할 때마다 해당 태스크의 우선순위를 높일 수 있다.
- 조기 태스크 종료: 뮤텍스는 또한 뮤텍스를 소유한 태스크가 실수로 삭제될 수 없는 삭제 안전성을 제공할 수도 있다.
- 종료 데드락: 뮤텍스를 소유한 태스크가 어떤 이유로든 종료되면 OS는 뮤텍스를 해제하고 이 상태를 기다리는 태스크에 신호를 보낼 수 있다.
- 재귀 데드락: 태스크는 재진입 뮤텍스를 잠근 횟수만큼 해제하는 경우 여러 번 잠글 수 있다.
- 실수로 해제: 해제하는 태스크가 뮤텍스의 소유자가 아닌 경우 뮤텍스 해제 시 오류가 발생한다.
세마포어는 어떤 자원이 몇 개 사용 가능한지를 나타내는 기록이라고 생각하면 이해하기 쉽다. 그리고 그 자원을 사용하거나 해제할 때 그 기록을 "안전하게"(즉, 경합 상태가 발생하지 않도록) 수정하고, 필요에 따라 자원을 사용할 수 있을 때까지 대기하는 연산이 결합되어 있다. 세마포어는 경합 상태를 방지하는 편리한 도구이지만, 세마포어를 사용한다고 해서 프로그램에서 경합 상태가 완전히 없어지는 것을 보장하는 것은 아니다. 임의 개수의 자원을 다루는 세마포어를 '''카운팅 세마포어''', 값이 0과 1로 제한되는(잠금/해제, 사용 가능/사용 불가 의미) 세마포어를 '''이진 세마포어'''라고 부른다. 후자는 뮤텍스와 동등한 기능을 가진다.
대부분의 경우, 뮤텍스에는 "소유자" 개념이 있다. 즉, 뮤텍스를 잠근 실행 단위만이 그것을 언락할 수 있다. 대조적으로 세마포어에는 그러한 제한이 없다.
따라서 기본적으로, 뮤텍스는 실행 단위 등의 실행 실체와 결부되어 있으며, 세마포어는 자원과 결부되어 있다.
9. 활용 환경 (플랫폼별)
POSIX, Windows, .NET Framework, 자바 등 다양한 환경에서 세마포어를 활용할 수 있다. 각 환경에 따라 제공되는 함수나 클래스를 사용하여 세마포어를 생성, 사용, 제거할 수 있으며, 프로세스 간 동기화 또는 스레드 간 동기화에 활용할 수 있다.
9. 1. POSIX
POSIX를 준수하는 환경에서는 `semaphore.h`에 선언된 형식과 함수를 사용할 수 있다. `sem_init()`로 이름 없는 세마포어를 생성하거나[24], `sem_open()`으로 이름 있는 세마포어를 생성 또는 열 수 있다.[25] `sem_init()`의 인자 `pshared`에 0이 아닌 값을 지정함으로써, 프로세스 간에 공유되는 세마포어를 생성할 수도 있다. `sem_wait()`로 세마포어의 값을 감소시키고(잠금), `sem_post()`로 세마포어의 값을 증가시킨다(잠금 해제). `sem_destroy()`로 이름 없는 세마포어를 파괴하고, `sem_close()`로 이름 있는 세마포어를 닫는다.9. 2. Windows
Windows의 Win32 API에서는 `CreateSemaphore()` 함수로 세마포어를 생성 또는 열 수 있다. `WaitForSingleObject()` 함수로 세마포어를 대기한다(카운트 감소). `ReleaseSemaphore()` 함수로 세마포어를 해제한다(카운트 증가). `CloseHandle()` 함수로 세마포어의 핸들을 닫는다(객체 파기)[26]。`OpenSemaphore()` 함수로 기존의 명명된 세마포어를 열 수 있다[27]。스레드 간 동기화뿐만 아니라 프로세스 간 동기화에도 사용할 수 있다[28][29]。MFC에는 C++(C++)의 생성자/소멸자에 의한 RAII 기구를 이용한, Win32 동기화 객체의 래퍼 클래스 `CSemaphore`가 준비되어 있다[30](실제로는 `CSingleLock`과 조합하여 사용하는 경우가 많다[31]).
9. 3. .NET
.NET Framework 2.0 이후에는 `System.Threading.Semaphore` 클래스를 사용한다. 버전 1.1 이전에는 직접 제작하거나 Win32 API를 호출해야 했다. 로컬 세마포어는 스레드 간 동기화에만 사용할 수 있지만, 명명된 시스템 세마포어는 프로세스 간 동기화에도 사용할 수 있다[32]。`Semaphore` 클래스는 .NET Core 및 .NET 5 이후에도 사용할 수 있지만, 윈도우 이외의 플랫폼에서는 스레드 간 동기화에만 사용할 수 있으며, 프로세스 간 동기화에는 사용할 수 없다[33]。.NET Framework 4.0 이후에는 동일 프로세스 내의 스레드 간 동기화라면, 경량화된 `System.Threading.SemaphoreSlim` 클래스를 이용할 수 있다[34]。
9. 4. Java
자바에서는 `Semaphore영어` 클래스를 사용한다. 자바 5 이후부터 사용 가능하다.9. 5. 기타 언어 (Perl, Python, Swift)
Perl에서는 `Thread::Semaphore` 모듈이나, 최근에는 `Coro::Semaphore` 모듈 등을 사용한다.파이썬에서는 `threading` 모듈 안에 `Semaphore` 클래스가 준비되어 있다.
Swift에서는 Grand Central Dispatch영어의 기능으로 `Dispatch` 프레임워크 내에 `DispatchSemaphore` 클래스가 제공된다.
참조
[1]
EWD
Over de sequentialiteit van procesbeschrijvingen
1962
[2]
서적
The Little Book of Semaphores
http://greenteapress[...]
[3]
서적
Foundations of Multithreaded, Parallel, and Distributed Programming
Addison-Wesley
1999
[4]
서적
Modern Multithreading: Implementing, Testing, and Debugging Multithreaded Java and C++/Pthreads/Win32 Programs
Wiley
2005
[5]
서적
Nonsequential and Distributed Programming with Go
Springer
2021
[6]
서적
Operating System Concepts
John Wiley & Sons. Inc
[7]
EWD
[8]
EWD
MULTIPROGAMMERING EN DE X8
[9]
문서
http://www.wsu.edu/~[...]
[10]
뉴스
(PATCH 1/19) MUTEX: Introduce simple mutex implementation
https://lkml.org/lkm[...]
Linux Kernel Mailing List
2005-12-19
[11]
웹사이트
Linux Kernel hacking HOWTO
http://www.linuxgril[...]
[12]
간행물
Semaphores in Plan 9
http://doc.cat-v.org[...]
[13]
Javadoc
[14]
웹사이트
exec.library/Procure
http://amigadev.elow[...]
2016-09-19
[15]
웹사이트
exec.library/Vacate
http://amigadev.elow[...]
2016-09-19
[16]
문서
Cooperating sequential processes (EWD-123)
http://www.cs.utexas[...]
1965-09
[17]
서적
The Little Book of Semaphores
http://greenteapress[...]
[18]
Harvnb
[19]
문서
Over Seinpalen (EWD-74)
http://www.cs.utexas[...]
[20]
문서
MULTIPROGRAMMERING EN DE X8 (EWD-51)
http://www.cs.utexas[...]
[21]
문서
http://www.wsu.edu/~[...]
[22]
뉴스
(PATCH 1/19) MUTEX: Introduce simple mutex implementation
http://lkml.org/lkml[...]
Linux Kernel Mailing List
2005-12-19
[23]
웹사이트
Linus Kernel hacking HOWTO
http://www.linuxgril[...]
[24]
웹사이트
sem_init | The Open Group Base Specifications Issue 7, 2018 edition IEEE Std 1003.1-2017
https://pubs.opengro[...]
[25]
웹사이트
sem_open | The Open Group Base Specifications Issue 7, 2018 edition IEEE Std 1003.1-2017
https://pubs.opengro[...]
[26]
웹사이트
Using Semaphore Objects - Win32 apps | Microsoft Learn
https://learn.micros[...]
[27]
웹사이트
OpenSemaphoreW function (synchapi.h) - Win32 apps | Microsoft Learn
https://learn.micros[...]
[28]
웹사이트
CreateSemaphoreA function (winbase.h) - Win32 apps | Microsoft Learn
https://learn.micros[...]
[29]
웹사이트
CreateSemaphoreW function (synchapi.h) - Win32 apps | Microsoft Learn
https://learn.micros[...]
[30]
웹사이트
CSemaphore Class | Microsoft Learn
https://learn.micros[...]
[31]
웹사이트
Multithreading: How to Use the MFC Synchronization Classes | Microsoft Learn
https://learn.micros[...]
[32]
웹사이트
Semaphore Class (System.Threading) | Microsoft Learn
https://learn.micros[...]
[33]
웹사이트
同期プリミティブの概要 - .NET | Microsoft Learn
https://learn.micros[...]
[34]
웹사이트
Semaphore と SemaphoreSlim - .NET | Microsoft Learn
https://learn.micros[...]
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com