semWait
: Process 를 Block 시킬 수 있는 명령어이다.semSignal
: 내가 아니라 Queue에 있는 프로세스를 Ready로 옮겨 실행할 수 있게 해준다.Semaphore는 Critical Section뿐 아니라, 많은 동기화 문제에 사용된다.
↳ 특히 Critical Section을 다룰 때는 무조건 초기 세마포 변수 값을 1로 설정해야 한다!
Race Condition
: 실행 순서에 따라 결과가 어떻게 될지 모르는 상황어느 위치에 semWait과 semSignal를 사용할지 잘 선택해야 한다.
하늘색 네모 부분은 해당 프로세스가 Blocked Queue 에서 대기하고 있다는 의미이다.
여러 프로세스들이 Critical Section에 진입하려고 문에 서 있다고 가정해보자.
Process 2가 먼저 문을 열고 Critical Section에 들어가서 문을 닫아버리고 s 값을 0으로 바꾼다.
이후 접근하는 프로세스들은 s 값이 음수가 되기 때문에 Blocked Queue에 들어가서 대기하게 된다.
Process2가 Critical Section 실행을 마친 후,
나갈 때 semSignal
명령어를 통해 대기하던 프로세스를 문 안으로 데려온다.
s > 0 (s == 1) 이되면, 문이 다시 열리는 것을 의미한다.
지금까지 진행했던 Semaphore 는 Counting Semaphore 이다.
Binary Semaphore는 Semaphore 값을 0 아니면 1만 설정 가능하다.
Binary Semaphore는 작은 group의 문제들을 해결할 수 있다.
Binary Semaphore 에서는 Semaphore 값 S가 0이 되면 무조건 Block을 시킨다.
→ 몇 개가 block 되어 있는지는 알 수 없다.
Queue에서 프로세스를 꺼낼 때 Semaphore 값 S를 보고는 몇개의 프로세스가 Queue에 있는 지 알 수 없다.
⇒ Queue 자체를 확인한다. → isEmpty()
데이터를 생성해서 Buffer에 쓴다.
Buffer에서 데이터를 읽어서 작업한다.
Producer Process와 Consumer Process는 한 번에 하나만 Buffer에 접근할 수 있다!
위의 버퍼를 I/O Buffer라고 가정하자.
Memory Data → I/O buffer → I/O Device 출력
두 Producer Process 가 동시에 버퍼에 접근하여 데이터를 쓸 수 있고, 두 Concumer Process가 동시에 버퍼에 접근하여 데이터를 출력할 수 있다.
이렇게 프로세스가 동시에 버퍼에 접근하는 문제는 Critical Section 해결 방법과 동일하게 해결할 수 있다!
Critical Section 해결 방법과 동일하게 해결할 수 있다!
Consumer Process 가 Buffer에서 데이터를 가져갈 수 없다.
Producer Process 가 Buffer에 데이터를 쓸 수 없다.
⇒ Buffer의 크기 문제를 해결해야 한다!
처음엔 버퍼가 비어 있다.
Producer Process가 다음에 Data를 쓸 위치
Consumer Process가 다음에 Data를 읽을 위치
in > out
out > in
위의 그림에서 아래 그림으로 바뀌는 동안, Producer 는 b[5] 부터 한바퀴를 돌아 b[2]까지 데이터를 쓴 것이고, Consumer는 b[4]까지 데이터를 읽은 것이다.
in == out → 데이터가 빈 것이라고 볼 수 있다.
in == out → 데이터가 꽉 것이라고 볼 수 있다.
⇒ 그렇다면 어떻게 Buffer가 비고 꽉찼는지 구분할 수 있을까?
↳ 버퍼가 꽉차기 전에 버퍼 하나를 비워둔다.
⇒ 즉, in == out-1 → 버퍼가 꽉 찼다고 인식한다.
좋은 Solution이 아니다.
⇒ Semaphore 를 이용해서 해결하자
semWait
: 상황에 따라 프로세스를 기다리게 한다.semSignal
: 상황에 따라 기다리고 있던 프로세스를 풀어준다.Producer Process, Consumer Process, Critical Section 의 Blocked Queue를 하나로 관리하지 않는다.
⇒ 3개의 Queue에서 3개의 Semaphore를 관리해야 한다.
/* program boundedbuffer */
const int sizeofbuffer = /* buffer size */;
semaphore s = 1, n = 0, e = sizeofbuffer;
void producer()
{
while(true) {
produce();
semWait(e);
semWait(s);
append();
semSignal(s);
semSignal(n); // 버퍼에 데이터가 하나 증가한다. + blocked된 Consumer가 있으면 다시 Ready 상태로 깨워준다.
}
}
void consumer()
{
while(true) {
semWait(n);
semWait(s);
take();
semSignal(s);
semSignal(e); // 버퍼에 데이터가 하나 지워진다. + blocked된 Procuder가 있으면 다시 Ready 상태로 깨워준다.
consume();
}
}
void main()
{
parbegin(producer, consumer); // 여러개의 producer들과 consumer 들이 함께 작업한다.
}
semSignal(n)의 의미:
버퍼에 데이터가 하나 증가한다. + blocked된 Consumer가 있으면 다시 Ready 상태로 깨워준다.
semSignal(e)의 의미:
버퍼에 데이터가 하나 지워진다. + blocked된 Procuder가 있으면 다시 Ready 상태로 깨워준다.
즉, Producer와 Consumer는 서로 blocked 된 프로세스를 깨워준다.
세개의 Semaphore
를 사용한다.
→ n, e, s
⇒ Semaphore 값이 어떻게 변하는지 알아야 한다.
Critical Section 처리 Semaphore
Critical Section에 여러 프로세스가 들어가려고 대기하면, s 값이 음수가 될 수 있다.
초기값: 버퍼의 크기
어느 순간 버퍼에 있는 빈칸의 수
초기값: 0
어느 순산 버퍼에 있는 데이터의 개수
버퍼의 크기 (단, n > 0, e > 0 ⇒ blocked queue에 프로세스가 대기 중이지 않은 경우만 성립한다.)
n == 0 → 버퍼가 비었다.
e == 0 → 버퍼가 가득 찼다.
/* program boundedbuffer */
const int sizeofbuffer = /* buffer size */;
semaphore s = 1, n = 0, e = sizeofbuffer;
void producer()
{
while(true) {
produce();
/*순서 교체*/
semWait(s);
semWait(e);
/*순서 교체*/
append();
semSignal(s);
semSignal(n); // 버퍼에 데이터가 하나 증가한다. + blocked된 Consumer가 있으면 다시 Ready 상태로 깨워준다.
}
}
void consumer()
{
while(true) {
/*순서 교체*/
semWait(s);
semWait(n);
/*순서 교체*/
take();
semSignal(s);
semSignal(e); // 버퍼에 데이터가 하나 지워진다. + blocked된 Procuder가 있으면 다시 Ready 상태로 깨워준다.
consume();
}
}
void main()
{
parbegin(producer, consumer); // 여러개의 producer들과 consumer 들이 함께 작업한다.
}
코드 순서를 변경할 때, 숫자 값의 변화는 없어도, Deadlock이 발생할 수 있다.
semWait(e) ↔ semWait(s) 이나 semWait(n) semWait(s) 를 바꾸는 경우 문제 발생
→ 2가지 case가 존재한다.
buffer가 가득찬 경우
↳ producer가 semWait(e);
에서 걸려서 세마포 s 가 semSignal이 되지 않아 다음에 온 producer들이 s Queue에만 producer가 계속 쌓이는 deadlock 이 발생한다. Consumer가 semSignal(e)를 해도 block이 풀리지 않는다.
buffer가 빈 경우
↳ consumer가 semWait(n);
에서 걸려서 세마포 s 가 semSignal이 되지 않아 다음에 온 Consumer들이 s Queue에만 producer가 계속 쌓이는 deadlock 이 발생한다. producer가 semSignal(n)를 해도 block이 풀리지 않는다.
가능하다!
세마포어는 보호할 공유 자원의 수에 따라 값을 가지며, 세마포어 값은 원자적으로 증가 및 감소할 수 있어야 한다. swap&compare 명령을 사용하여 세마포어 값을 변경하면, 해당 연산은 원자적으로 수행된다. 따라서, 세마포어를 보호하는데 사용될 수 있다.
예를 들어, 세마포어 값이 0일 때, 세마포어를 획득하는 프로세스는 swap&compare 명령을 사용하여 세마포어 값을 1로 변경하고, 이 명령이 성공하면(즉, 세마포어 값이 0에서 1로 변경됨), 해당 프로세스는 보호된 자원에 대한 액세스를 얻을 수 있다. 다른 프로세스가 이미 세마포어를 소유하고 있으면, swap&compare 명령은 실패하고 해당 프로세스는 대기열에 추가된다.
Swap&compare를 사용하여 세마포어를 구현할 때, 보통 하나의 변수만 필요하다. 이 변수는 세마포어의 값을 저장하며, 세마포어를 보호하기 위해 원자적 연산을 수행하는 데 사용된다.