세마포어는 컴퓨터 시스템에서 여러 프로세스의 활동을 조정하는 데 사용되는 일반적인 변수입니다. 이는 상호 배제를 강제하고, 경쟁 조건을 피하고, 프로세스 간 동기화를 구현하는 데 사용됩니다.
세마포어를 사용하는 프로세스는 대기(P)와 신호(V)라는 두 가지 작업을 제공합니다. 대기 연산은 세마포어 값을 감소시키고, 신호 연산은 세마포어 값을 증가시킵니다. 세마포어의 값이 0이면 대기 작업을 수행하는 모든 프로세스는 다른 프로세스가 신호 작업을 수행할 때까지 차단됩니다.
세마포어는 한 번에 하나의 프로세스에서만 실행되어야 하는 코드 영역인 임계 섹션을 구현하는 데 사용됩니다. 세마포어를 사용하여 프로세스는 공유 메모리나 I/O 장치와 같은 공유 리소스에 대한 액세스를 조정할 수 있습니다.
세마포어는 특정 동기화 프리미티브를 통해서만 사용할 수 있는 특별한 종류의 동기화 데이터입니다. 프로세스가 세마포어에 대해 대기 작업을 수행할 때 해당 작업은 세마포어의 값이 0보다 큰지 여부를 확인합니다. 그렇다면 세마포어의 값을 감소시키고 프로세스가 계속 실행되도록 합니다. 그렇지 않으면 세마포어의 프로세스를 차단합니다. 세마포어에 대한 신호 연산은 세마포어에서 차단된 프로세스를 활성화하거나 세마포어의 값을 1씩 증가시킵니다. 이러한 의미로 인해 세마포어는 카운팅 세마포라고도 합니다. 세마포어의 초기 값은 대기 작업을 통과할 수 있는 프로세스 수를 결정합니다.
세마포어에는 두 가지 유형이 있습니다.
- 바이너리 세마포 –
이는 뮤텍스 잠금이라고도 합니다. 0과 1의 두 가지 값만 가질 수 있습니다. 해당 값은 1로 초기화됩니다. 여러 프로세스의 임계 섹션 문제에 대한 솔루션을 구현하는 데 사용됩니다. - 카운팅 세마포어 –
그 값은 무제한 도메인에 걸쳐 있을 수 있습니다. 여러 인스턴스가 있는 리소스에 대한 액세스를 제어하는 데 사용됩니다.
이제 어떻게 그렇게 되는지 살펴보겠습니다.
먼저, 세마포어 변수의 값에 액세스하고 변경하는 데 사용할 수 있는 두 가지 작업을 살펴보세요.

P 및 V 작동에 관한 몇 가지 사항:
- P 동작은 wait, sleep, down 동작이라고도 하고, V 동작은 signal, wake-up, up 동작이라고도 합니다.
- 두 작업 모두 원자적이며 세마포어는 항상 하나로 초기화됩니다. 여기서 원자성은 선점 없이 동시에 읽기, 수정 및 업데이트가 발생하는 변수, 즉 읽기, 수정 및 업데이트 사이에 변수를 변경할 수 있는 다른 작업이 수행되지 않음을 의미합니다.
- 임계 섹션은 프로세스 동기화를 구현하기 위해 두 작업으로 둘러싸여 있습니다. 아래 이미지를 참조하세요. 프로세스 P의 임계 섹션은 P와 V 작업 사이에 있습니다.

이제 상호 배제를 어떻게 구현하는지 살펴보겠습니다. 두 개의 프로세스 P1과 P2가 있고 세마포어 s는 1로 초기화됩니다. 이제 P1이 임계 섹션에 들어간다고 가정하면 세마포 s의 값은 0이 됩니다. 이제 P2가 임계 섹션에 들어가고 싶다면 s가 될 때까지 기다립니다.> 0, 이는 P1이 임계 섹션을 완료하고 세마포어에서 V 작업을 호출할 때만 발생할 수 있습니다.
이런 식으로 상호 배제가 달성됩니다. 바이너리 세마포어에 대한 자세한 내용은 아래 이미지를 참조하세요.

구현: 바이너리 세마포어
struct semaphore { enum value(0, 1); // q contains all Process Control Blocks (PCBs) // corresponding to processes got blocked // while performing down operation. Queue큐; }; P(세마포어 s) { if (s.value == 1) { s.value = 0; } else { // 대기 큐에 프로세스를 추가합니다. q.push(P) sleep(); } } V(세마포어 s) { if (s.q가 비어 있음) { s.value = 1; } else { // 대기 중인 대기열에서 프로세스를 선택합니다. Process p = q.front(); // CS에 대해 // 전송되었으므로 프로세스를 대기에서 제거합니다. q.pop(); 웨이크업(p); } } // 이 코드는 Susobhan Akhuli에 의해 수정되었습니다.> 씨 #include #include #include struct semaphore{ Queue큐; 정수 값; }; void P(struct semaphore s) { if (s.value == 1) { s.value = 0; } else { s.q.push(P); 잠(); } } void V(세마포어 s) { if (s.q가 비어 있음) { s.value = 1; } else { // 대기 대기열 프로세스에서 프로세스 가져오기 p = q.front(); // 대기 중인 프로세스를 제거합니다. q.pop(); 웨이크업(p); } } int main() { printf('이것은 헤미쉬입니다!!'); // 이 코드는 Himesh Singh Chauhan이 제공한 것입니다. return 0; } // 이 코드는 Susobhan Akhuli에 의해 수정되었습니다.> 자바 import java.util.*; class Semaphore { public enum Value { Zero, One } public Queueq = 새로운 LinkedList(); 공개 값 값 = Value.One; public void P(세마포어 s, 프로세스 p) { if (s.value == Value.One) { s.value = Value.Zero; } else { // 대기 중인 큐에 프로세스를 추가합니다. q.add(p); p.수면(); } } public void V(세마포어 s) { if (s.q.size() == 0) { s.value = Value.One; } else { // 대기 중인 큐에서 프로세스를 선택합니다. Process p = q.peek(); // 프로세스가 CS에 대해 전송되었으므로 대기 상태에서 // 제거합니다. q.remove(); p.Wakeup(); } } }> 파이썬3 from enum import Enum from queue import Queue class Semaphore: class Value(Enum): Zero = 0 One = 1 def __init__(self): self.q = Queue() self.value = Semaphore.Value.One def P(self, s, p): if s.value == Semaphore.Value.One: s.value = Semaphore.Value.Zero else: # add the process to the waiting queue s.q.put(p) p.Sleep() def V(self, s): if s.q.qsize() == 0: s.value = Semaphore.Value.One else: # select a process from waiting queue p = s.q.queue[0] # remove the process from waiting as it has # been sent for CS s.q.get() p.Wakeup()>
씨# using System.Collections.Generic; class Semaphore { public enum value { Zero, One } public Queueq = 새 대기열(); public void P(세마포어 s, 프로세스 p) { if (s.value == value.One) { s.value = value.Zero; } else { // 대기 중인 큐에 프로세스를 추가합니다. q.Enqueue(p); p.수면(); } } public void V(세마포 s) { if (s.q.Count == 0) { s.value = value.One; } else { // 대기 중인 큐에서 프로세스를 선택합니다. Process p = q.Peek(); // CS에 대해 전송된 // 프로세스를 대기 상태에서 제거합니다. q.Dequeue(); p.Wakeup(); } } }> 자바스크립트 class Semaphore { constructor() { this.value = 0; // q contains all Process Control Blocks (PCBs) // corresponding to processes got blocked // while performing down operation. this.q = []; } P() { if (this.value == 1) { this.value = 0; } else { // add the process to the waiting queue this.q.push(P); sleep(); } } V() { if (this.q.length == 0) { this.value = 1; } else { // select a process from waiting queue let p = this.q.shift(); // remove the process from waiting as it has been // sent for CS wakeup(p); } } }> 위의 설명은 0과 1의 두 값만 취하고 상호 배제를 보장할 수 있는 이진 세마포에 대한 것입니다. 1보다 큰 값을 취할 수 있는 카운팅 세마포어라는 또 다른 유형의 세마포어가 있습니다.
이제 인스턴스 수가 4인 리소스가 있다고 가정합니다. 이제 S = 4로 초기화하고 나머지는 바이너리 세마포와 동일합니다. 프로세스가 해당 리소스를 원할 때마다 P를 호출하거나 기능을 기다리고, 완료되면 V 또는 신호 기능을 호출합니다. S 값이 0이 되면 프로세스는 S가 양수가 될 때까지 기다려야 합니다. 예를 들어, 4개의 프로세스 P1, P2, P3, P4가 있고 모두 S(4로 초기화됨)에서 대기 작업을 호출한다고 가정합니다. 다른 프로세스 P5가 리소스를 원하면 4개의 프로세스 중 하나가 신호 기능을 호출하고 세마포어 값이 양수가 될 때까지 기다려야 합니다.
제한 사항:
- 세마포어의 가장 큰 한계 중 하나는 우선순위 반전입니다.
- 교착 상태, 프로세스가 절전 상태가 아닌 다른 프로세스를 깨우려고 한다고 가정합니다. 따라서 교착 상태가 무기한 차단될 수 있습니다.
- 운영 체제는 대기하고 세마포에 신호를 보내는 모든 호출을 추적해야 합니다.
이 세마포어 구현의 문제점:
세마포어의 주요 문제점은 바쁜 대기가 필요하다는 것입니다. 프로세스가 임계 구역에 있으면 임계 구역에 들어가려는 다른 프로세스는 임계 구역을 어떤 프로세스도 차지하지 않을 때까지 대기하게 됩니다. 프로세스가 기다릴 때마다 세마포어 값(P 작업에서 (s==0); 이 줄을 보세요)과 CPU 낭비 주기를 지속적으로 확인합니다.
연결 자바 문자열
잠금을 기다리는 동안 프로세스가 계속 회전하므로 스핀록이 발생할 가능성도 있습니다. 이를 방지하기 위해 아래에 또 다른 구현이 제공됩니다.
구현: 세마포어 계산
CPP struct Semaphore { int value; // q contains all Process Control Blocks(PCBs) // corresponding to processes got blocked // while performing down operation. Queue큐; }; P(세마포어 s) { s.value = s.value - 1; if (s.값< 0) { // add process to queue // here p is a process which is currently executing q.push(p); block(); } else return; } V(Semaphore s) { s.value = s.value + 1; if (s.value <= 0) { // remove process p from queue Process p = q.pop(); wakeup(p); } else return; }> 자바 import java.util.LinkedList; import java.util.Queue; // semaphore class class Semaphore { // our value int value; Queue큐; 공개 세마포(int value) { this.value = value; q = 새로운 LinkedList(); } public void P(프로세스 p) { 값--; if (값< 0) { q.add(p); p.block(); } } public void V() { value++; if (value <= 0) { Process p = q.remove(); p.wakeup(); } } }> 파이썬3 import heapq # Global Variable to track the Processes going into Critical Section COUNTER=1 class Semaphore: def __init__(self,value): # Value of the Semaphore passed to the Constructor self.value=value # The Waiting queue which will be using the heapq module of Python self.q=list() def getSemaphore(self): ''' Function to print the Value of the Semaphore Variable ''' print(f'Semaphore Value: {self.value}') def block(process): print(f'Process {process} Blocked.') def wakeup(process): print(f'Process {process} Waked Up and Completed it's work.') def P(s): global COUNTER s.value=s.value-1 if(s.value<0): heapq.heappush(s.q,COUNTER) block(COUNTER) else: print(f'Process {COUNTER} gone inside the Critical Section.') COUNTER+=1 return def V(s): global COUNTER s.value=s.value+1 if(s.value<=0): p=heapq.heappop(s.q) wakeup(p) COUNTER-=1 else: print(f'Process {COUNTER} completed it's work.') COUNTER-=1 return # Can Pass the Value of the Counting Semaphore to the Class Constructor # Example for Counting Semaphore value as 2 s1=Semaphore(2) s1.getSemaphore() P(s1) s1.getSemaphore() P(s1) s1.getSemaphore() P(s1) s1.getSemaphore() V(s1) s1.getSemaphore() V(s1) s1.getSemaphore() V(s1) s1.getSemaphore() # This Code is Contributed by Himesh Singh Chauhan> 씨# using System.Collections.Generic; public class Semaphore { public int value; // q contains all Process Control Blocks(PCBs) // corresponding to processes got blocked // while performing down operation. Queueq = 새 대기열(); public void P(프로세스 p) { 값--; if (값< 0) { // add process to queue q.Enqueue(p); p.block(); } } public void V() { value++; if (value <= 0) { // remove process p from queue Process p = q.Dequeue(); p.wakeup(); } } }> 자바스크립트 // Define a Semaphore object function Semaphore() { this.value = 0; this.q = []; // Initialize an array to act as a queue } // Implement the P operation Semaphore.prototype.P = function(p) { this.value = this.value - 1; if (this.value < 0) { // Add process to queue this.q.push(p); // Assuming block() and wakeup() functions are defined elsewhere block(); } }; // Implement the V operation Semaphore.prototype.V = function() { this.value = this.value + 1; if (this.value <= 0) { // Remove process p from queue var p = this.q.shift(); // Assuming wakeup() function is defined elsewhere wakeup(p); } }; // This code is contributed by Susobhan Akhuli> 이 구현에서는 프로세스가 기다릴 때마다 해당 세마포어와 관련된 프로세스의 대기 대기열에 추가됩니다. 이는 해당 프로세스의 시스템 호출 block()을 통해 수행됩니다. 프로세스가 완료되면 신호 함수를 호출하고 대기열에 있는 한 프로세스가 다시 시작됩니다. wakeup() 시스템 호출을 사용합니다.
세마포어의 장점:
- 프로세스 동기화를 위한 간단하고 효과적인 메커니즘
- 여러 프로세스 간 조정 지원
- 공유 리소스를 관리하는 유연하고 강력한 방법을 제공합니다.
- 프로그램에서 중요한 섹션을 구현하는 데 사용할 수 있습니다.
- 경쟁 조건을 방지하는 데 사용할 수 있습니다.
세마포어의 단점:
- 대기 및 신호 작업과 관련된 오버헤드로 인해 성능 저하가 발생할 수 있습니다.
- 잘못 사용하면 교착 상태가 발생할 수 있습니다.
- 1965년 Dijkstra가 제안한 것으로, 세마포어라고 알려진 간단한 정수 값을 사용하여 동시 프로세스를 관리하는 매우 중요한 기술입니다. 세마포어는 단순히 스레드 간에 공유되는 정수 변수입니다. 이 변수는 임계 구역 문제를 해결하고 다중 처리 환경에서 프로세스 동기화를 달성하는 데 사용됩니다.
- 올바르게 사용하지 않으면 프로그램 성능 문제가 발생할 수 있습니다.
- 디버깅 및 유지 관리가 어려울 수 있습니다.
- 올바르게 사용하지 않으면 경쟁 조건 및 기타 동기화 문제가 발생하기 쉽습니다.
- 서비스 거부 공격과 같은 특정 유형의 공격에 취약할 수 있습니다.