일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 문제풀이
- 북리뷰
- bfs
- 컴공
- 코딩
- vector
- 구현
- cs
- 개발
- 자료구조
- OS
- c++
- Stack
- 오에스
- 알고리즘
- 브루트포스
- 컴퓨터공학과
- 백준
- Computer science
- coding
- 컴공과
- DP
- 정석학술정보관
- 코테
- 오퍼레이팅시스템
- 스택
- 너비우선탐색
- Operating System
- 정석
- 그래프
- Today
- Total
Little Jay
[OS] Synchronization V (Producer & Consumer Problem) 본문
[OS] Synchronization V (Producer & Consumer Problem)
Jay, Lee 2022. 12. 16. 20:23Producer and Consumer Problem
계속해서 생산자 소비자 문제를 다루고 있다. 생산자 소비자 문제 중 생산자가 무한히 자원을 생산할 수 있다고 가정하자. 그렇기 때문에 우리가 집중해야 할 것은 소비자 파트이다. 상한은 고려할 필요가 없고 하한을 고려한다. 소비자는 단순히 empty 상태이면 wait을 하면 된다. 이 wait은 결국 어떤 data가 채워지기를 기다리는 상태이다. 앞에서 언급한 V operation과 같은 기능이다. 다시 정리를 하자면 생산자는 P, 소비자는 V Operation이다.
Broken Scenario
이와 같은 생산자와 소비자고 있다고 해보자. 얼핏 보면 이렇게 생산자 소비자 문제를 구현 하는 것이 맞아 보인다. 그러나 이 코드에는 명백한 문제점이 있다. process가 두개 있다고 해보자. 그렇자면 얼핏 이 코드는 잘 돌아갈 것으로 보인다. 하지만 만약 process가 2개 이상이라면? 운영체제를 돌리고 chrome만 켜더라도 Process의 수는 엄청나게 많아진다. 따라서 2개라는 한정된 자원에서 논의를 하는 것이 아닌 General한 논의가 필요하다.
먼저 if 문을 살펴보자. 이 부분도 얼핏 보면 조건을 체크 해주는 것이기 때문에 working하는 것처럼 보인다. 그러나 이 부분의 처리는 while문으로 처리를 해줘야한다. 그 이유는 wait을 하고나서 signal에 의해 깨어난다면, state variable 즉 Critical Section에 있는 자원에 대한 체크를 다시 한 번 하고 넘어가야 한다.
Semantics of Signalling
- Mesa Semantic: signal로 깨어난 thread는 state variable을 recheck하는 방식
- Hoare Semantic: signal로 깨어난 thread는 바로 다시 실행되도록 강력하게 보장하는 방식
이 두가지 의 Signalling을 핸들하는 방법이 있는데 대부분의 System에서는 Mesa Semantic을 채택하고 있다. 단순히 if 문을 while로 바꿔주기만 하면 강력한 성능을 낼 수 있는 반면, Hoare 방식은 좀 복잡하게 구현을 해야한다.
그래서 한번 고쳐보았다. 그러나 이렇게 고쳐도 여전히 문제가 생긴다. 예를 들어서 세 개의 Process가 있는데 하나는 Consumer이고 두 개는 Producer라고 하자. 1번 Procuder 에서 부터 시작해서 count를 계속 들려나다가 어느 순간 MAX를 hit 해서 wait을 한다고 하자. 1번 Process는 count가 MAX 미만이 된 순간 Ready Process로 바뀐다. 운이 좋게 Scheduler가 Consumer를 선택했다고 하자. 그래서 Consumer도 자원을 소비를 하다가 어느 순간 자원의 수가 0이 되어서 wait을 해야하는데 이제 signal을 보낸다. 이제 문제는 Scheduler가 Sleep하고 있는 즉 wait 하고 있던 Process에게 signal을 보내야 하는데 엉뚱하게 다른 Ready Process Process에게 Signal을 보낼 수 있다. 그러면 sleep된 Process는 깨어날 수 없다. 문제는 이런 상황이다. 그러면 어떻게 최종적으로 이 문제를 해결할 수 있을까.
Solution
위 상황에서 확인해볼 수 있었던 것은 Consumer는 Producer를 깨워야 하고 반대로 Producer는 Consumer를 깨워야 한다. 따라서 종합적으로 보았을 때 Condition Variable을 두 개를 사용하면 될 것이다. 이렇게 하면 제대로 Process들이 엉키거나 Deadlock에 들어가지 않고 정상적으로 작동할 수 있을 것이다.
지금까지는 기본적인 pthread System Call들을 활용해 생산자 소비자 문제를 해결해보았다. pthread_cond_wait의 내부구현으로 편하게 구현을 할 수 있었다. 그러나 Semaphore로도 당연히 이 문제를 해결할 수 있다. Semaphore는 general하다. Mutual Exclusion을 위해서, Condition Synchronization을 위해 사용될 수 있다. Synchronization 3에서 Semaphore의 사용법에 대해서 간단히 올려놓았으니 바로 설명을 들어가겠다. 우리는 semaphore을 POSIX version으로 사용할 것이다. IPC로도 할 수 있겠지만 POSIX version이 훨씬 쓰기에 편하다.
By Semaphore
우리가 pthread 군을 배웠으므로 이렇게 구현할 수도 있다고 생각할 수 있다. 그러나 Semaphore는 pthread와는 다르게 General한 version이다. 위에서 사용한 pthread_cond_wait은 mutex를 일시적으로 해제하는 release code가 있었다. 따라서 임계영역에 있는 자원을 사용할 수 있었다. 그러나 Semaphore는 이러한 기능이 없다. 먼저 위의 코드처럼 먼저 mutex를 걸어버리고 만약 다른 process가 interleave한다면 아무도 자원을 사용하지 못하는 deadlock에 걸려버릴 수 있다. 따라서 위와 같이 구현하는 것이 아니라, 자원을 컨트롤 하는 바로 앞, 뒤에 mutex를 걸어주어야 한다. 따라서 아래와 같이 구현하면 간단하게 생산자 소비자 문제를 해결할 수 있다.
Disadvantage of Semaphore
그러나 Semaphore는 마냥 긍정적인 측면만 있는 것은 아니다. 특히 Generalize, 즉 Condition Synchronization에서는 주의를 해야하는데, P와 V operation이 코드 전체에 흩어져 있기 때문에 Debugging이 매우 힘들어질 수 있다. 따라서 이에 따른 Deadlock 발생이 일어나기 쉬운 구조를 가지고 있는 것은 사실이다. 따라서 Semaphore를 쓴다면 구조를 정말 철저히 파악하여 사용해야한다.