프로세스를 동시에 실행하는 경우 오버래핑
, CPU가 하나인 상황에서 서로 번갈아가면서 실행하는 경우 인터리빙
이라고 한다.
이때 발생하는 문제가 Concurrency 이다.
공유자원을 서로 읽기/쓰기할 때 순서가 어떻게 될지 모르는 상황.
누가 마지막으로 작업하냐에 따라 결과가 바뀐다.
CPU가 하나인 경우에도 프로세스들은 서로 번갈아가면서 인터리빙
상태에서 실행한다.
Race Condition을 없애는 방법
동시에 실행시키지 않는 코드 = Critical Section
즉, 한 프로세스의 Critical Section
코드가 진행되는 동안 다른 프로세스의 Critical Section
코드는 실행되지 않아야 한다.
Critical Section
에 진입Critical Section
에 진입할 의사가 있는지 나타낸다.Critical Section
진입Mutual Exclusion
- P0 Critical Section 진입
- flag[1] = false 확인
- flag[0] = true
- Critical Section 진입
- P1도 Critical Section 진입
- flag[0] = false 확인 ⇒ flag[0] = false임을 확인할 수 있는 시점은 P0가 flag[1] = false를 확인한 직후이다. ⇒ 가능
- flag[1] = true
Critical Section
에 들어간다.Critical Section
에 진입 = 상대 flag가 falseCritical Section
중간에 flag를 false로 바꾸는 과정은 없다.Critical Section
진입하려면 flag[0]이 false인 걸 확인해야 한다.Critical Section
동안 계속 trueMutual Exclusion 증명방법
두 프로세스가 동시에 작업하는 경우가 존재하는 경우가 없다는 것은 모순을 보여준다.
- P0가
Critical Section
진입
- flag[0] = true
- flag[1] = false 확인
Critical Section
진입- P1도 Critical Section 코드 동시 실행
- flag[1] = true
- flag[0] = false 확인 ⇒ flag[0]이 false인 시점은 flag[0] = true로 만들기 직전이다. 그런데 P1은 flag[1] = true로 만든 다음에 flag[0] = false인지 확인한다. ⇒ P1의 flag는 true인 동시에 false이여야 P0와 P1가 동시에 Critical Section에 진입할 수 있다.
- DeadLock 발생 가능
- 둘 이상의 프로세스가 상대편을 기다린다.
- 타임아웃이 flag를 True로 설정한 직후 발생
- Deadlock이 걸릴 확률은 낮기 때문에 위험하다.
Critical Section
에 들어가서 작업을 하고 나오면 들어간다.Critical Section
에 들어가는 사람은 상대 flag가 false인 것을 확인하고 들어간다.Mutual Exclusion 증명
- P0가
Critical Section
진입
- flag[0] = true
- flag[1] = false 확인
- Critical Section 진입
- P1도
Critical Section
진입
- flag[1] = true
- flag[0] = false 확인 ⇒ flag[0] = false인 시점은 flag[0] = true로 만들기 직전 또는 while문 내에서 flag[0] = false로 바뀌었을 때이다. 그런데 P1은 flag[1] = true로 만든 이후 flag[0] = false인지 확인한다. P0도 flag[1] = false임을 확인하고 들어가므로 동시 진입은 불가능하다.
- DeadLock X
- LiveLock이 발생 가능
- 한문장씩 서로 타임아웃
- DeadLock보다 가능성이 적다.
Critical Section
에 진입한다.Mutual Exclusion 증명
- P0 Critical Section 진입
- flag[0] = true
- flag[1] = false 확인
- Critical Section 진입
- P1 Critical Section 진입
- flag[1] = true
- flag[0] = false 확인 ⇒ 가능한 라인은 첫문장 실행 전 또는 while문 내에서 flag[0] = false로 바꾼 이후이다. 하지만 flag[1] = true로 바꾼 이후 flag[0] = false인지 확인한다. 따라서 flag[1]은 false인 동시에 true이여야만 동시에 Mutal Exclusion에 진입할 수 있다.
- DeadLock X
- LiveLock X
- 두 사람 다 동시에 양보를 하는 것이 아닌 두 사람 중 한 사람만 양보한다.
- turn 값으로 양보할 사람을 정한다.
- turn = 0이면, 0번이 양보받는다.
- 양보하는 사람은 안쪽 while문을 돈다.
- flag는
Critical Section
실행 해야하는지 여부
💡 어디에서 타임아웃이 발생할지 모른다.
Critical Section
에 들어갔다가 나온 프로세스가 상대편이 기다리고 있음에도 불구하고 다시Critical Section
에 진입하는 경우가 있을까?
- P0의
Critical Section
이 실행되는 동안 P1은 if문 내의 flag[1]=false인 상태에서 타임아웃- P0는
Critical Section
이 끝나자마자 타임아웃- P1은
while (turn == 0)
을 돌다가 타임아웃- P0는 다시
while (flag[1])
진행 ⇒ P1은 flag[1] = false로 만든 직후의 while문에서 타임아웃됐으므로 flag[1] = false인 상태로 유지- P0
Critical Section
재진입- 바깥쪽 while문을 돌다가 안쪽 while 문을 돌게되는 상황이 있을까?
- 불가능하다. 바깥쪽 while문에서 안쪽 while문으로 넘어가려면 turn값이 상대방으로 넘어가야 한다. turn을 넘겨주는 작업은 본인의
Critical Section
이 끝난 후에만 가능하다.- 안쪽 while문을 돌다가 바깥쪽 while 문을 돌게되는 상황이 있을까?
- P0의
Critical Section
이 끝나고 turn = 1까지만 실행하고 타임아웃- P1은 안쪽 while문 탈출하지만, flag[0] = false가 실되지 않았으므로 바깥 while문 반복
Critical Section
에 무한하게 재진입하는 상황이 있을까?존재하지 않는다. 재진입하는 데에 횟수 제한이 있다. 만약 P0가 한 번 재진입을 한 이후에는 turn값은 1로 계속 유지된다.
그러다 P0에서 타임아웃이 나면은 P1은 자신의 flag를 true로 다시 만들고 외부 반복문을 돌게된다.
P1에서 타임아웃 이후 P0로 돌아오게 되면 P0는 결국
while (flag[1])
에 걸리게 되고, turn값은 1 이므로 자신의 flag를 false로 바꾸고 기다리게 된다.
Critical Section
에 진입할 의사가 없으면 자신이 들어간다.while (flag[1] && turn == 1)
Mutual Exclusion 증명
- P0 Critical Section 진입
- flag[0] = true
- turn = 1
- flag[1] = false 또는 turn = 0인지 확인
- Critical Section 진입
- P1 Critical Section 진입
- flag[1] = true
- turn = 0
- flag[0] = false or turn = 1인지 확인 ⇒ 맨 처음 또는 P0가 turn = 1 실행한 이후에 가능 ⇒ 맨 처음이면 P0가 Critical Section 진입 불가 X/P0가 turn = 1 실행한 이후에 확인 → flag[1] = true && turn = 1인 상황이므로 P0가 Critical Section 진입 불가
- Deadlock 발생 X
- turn 값은 1 또는 0 이기 때문 에 둘 다 while문을 돌 수 없다.
- Livelock 발생 X
이렇게 세가지 형태의 Interaction이 발생하다보면 문제들이 발생한다.
s Algorithm과 Peterson
s Algorithm이 가지는 문제점while (compare_and_swap(&bolt, 0, 1) == 1)
Compare&Swap
을 시도하면 재진입할 수 있다.OS가 mutal exclusion을 비롯한 동기화 문제를 해결하기 위한 대표적인 툴이다.
구조체 변수
정수 변수와 큐를 가지고 있다.
정수 변수의 값을 이용해서 프로세스를 기다리게 하거나, 기다리는 프로세스한테 시그널을 준다.
세마포의 3가지 오퍼레이션
세마포는 변수값과 연결된 큐를 가지고 있다.
wait 연산 실행 시 값이 0보다 크거나 같으면 통과하고 그렇지 않으면 큐에 들어간다.
세마포의 음수값은 Blocked 큐에 들어있는 프로세스의 개수를 의미한다.
세마포가 양수일 때는 Wait 연산을 해도 Block되지 않고 통과한다. 즉, 양수값은 통과할 수 있는 프로세스의 개수를 의미한다.
이러한 Semaphore를 Counting Semaphore라고 한다.
semSignal을 실행한 사람이 큐에 있는 프로세스 중 하나를 깨운다. 어떤 프로세스를 깨울지는 시스템마다 다르다.
0 또는 1 값을 가진다.
현재 대기하는 프로세스 숫자나, 몇개 프로세스가 통과할 수 있는지에 대해 알 수 없다.
1이 아니고 값이 0인 경우 해당하는 프로세스를 큐에 넣고 Block 시킨다.
1이면 semWait 통과, 0인 경우 Block 된다.
Semaphore는 초기값 설정이 중요하다. 설정값에 따라 몇 명이 진입하는지 정해진다.
Critical Section에 나온후 semSignal 연산을 실행해서 Blocked 큐에 들어있는 프로세스 중 하나를 뽑는다.
Binary Semaphore를 이용해도 똑같다.
동시에 여러 프로세스 레디 큐에 있는 동일한 데이터를 가져가거나 데이터를 쓸 수도 있다. 따라서 어디에 데이터를 저장하거나 프로세스를 집어넣을지는 한 번에 한 사람만 결정해야 한다.
Producer는 버퍼에 데이터를 집어넣기 때문에 가득 차 있으면 데이터를 집어넣을 수 없으므로 기다려야 한다. Consumer는 데이터가 없으면 기다려야 한다.
버퍼가 꽉 찼다는 정보와 비어있다는 정보는 어떻게 알까?
in : Producer가 데이터를 저장할 위치
out : Consumer가 가져갈 데이터의 위치
버퍼가 비거나 꽉 찬 경우 둘 다 in과 out이 같은 위치에 있게 된다.
버퍼가 빈 상황과 꽉 차는 상황을 구분하기 위해 Producer는 (in + 1) % n == out 인 상황으로 체크한다.
위 코드는 이해하기 위한 코드이고, 실제 Semaphore에서는 Busy Waiting 없이 해결한다.
Semaphore는 버퍼를 가득 채울 수 있다.
만약 semWait(e) → semWait(s)가 아닌 semWait(s) → semWait(e)순서로 진행된다면?
Deadlock이 발생한다.
버퍼가 비어있는 상황
순서 s n e 초기값 1 0 10 consumer : semWait(s) 0 0 10 consumer : semWait(n) 0 -1(consumer Block) 10 producer : semWait(s) -1(producer Block) -1 10 버퍼가 꽉 찬 상황
순서 s n e 초기값 1 10 0 producer : semWait(s) 0 10 0 producer : semWait(e) 0 10 -1(producer Block) consumer : semWait(s) -1(consumer Block 10 -1
세마포의 두 가지 의미
- 값
- 프로세스들이 Block 됐을 때 대기하는 장소
- Procedure = function
- 모니터는 한 번에 한 프로세스만 진입 가능
- 만약 둘 이상의 프로세스가 동시에 모니터 안에 있는 function을 호출할 경우 두 프로세스 중 한 사람만 모니터 안에 들어가고 다른사람은 기다린다.
- 여러 프로세스가 동시에 모니터 안에 있는 function을 호출하는 경우 제일 먼저 모니터 안에 function을 호출한 사람만 모니터 안에 들어가고 나머지 사람들은 입구의 큐에 가서 순서대로 기다린다.
- 회색 부분 ⇒ 컨디션 변수들에 의해 만들어진 큐
cwait(n)
= n에 해당하는 큐에 가서 대기한다.csignal(n)
도 마찬가지로 값을 따지지 않는다.- 세마포 wait과 다르게 값을 따지지 않는다.
- urgent queue =
csignal
명령을 호출했을 때 들어가는 큐
csignal
로 깨어난 프로세스는 모니터 안으로 들어오게 된다. 만약 중간에csignal
을 사용한다면, 사용한 프로세스가 일시적으로 대피해있는 공간이urgent queue
이다.- 회색 공간에는 많은 프로레스가 대기하고 있을 수 있지만, 하얀색 공간에는 한 번에 한 프로세스만 있을 수 있다.
- 모니터 자체가 Mutual Exclusion을 지키는 Critical Section
모니터 자체가 Critical Section이기 때문에 동기화만 신경쓰면 된다. 즉, 버퍼가 가득 찼는지 아니면 버퍼가 비었는지만 확인한다.
이런 것들을 확인하기 위해 컨디션 변수를 사용한다.
cwait(c)
= 컨디션 변수 c와 연결된 큐에 들어가서 기다린다.csignal(c)
= 컨디션 변수 c와 연결된 큐에서 대기중인 프로세스 중 하나를 꺼내서 실행시킨다.csignal(notempty)
실행csignal(notfull)
수행💡 Monitor에서 Semaphore의 Deadlock 발생 코드의 순서와 유사하지만 Deadlock이 발생하지 않는 이유
버퍼가 비거나 꽉 차서 대기하는 경우 모니터 밖에서 대기하기 때문이다.
cwait
을 수행했는데 버퍼가 가득 차있으면 모니터(= Critical Section) 밖의 회색 영역의 큐에서 대기한다.대기하고 있는 사이에 Consumer가 모니터 안에 들어와서 데이터를 사용할 수 있다.
Process A | Process B | 가능 여부 |
---|---|---|
Writer | Writer | X |
Writer | Reader | X |
Reader | Writer | X |
Reader | Reader | O |
Reader와 Writer는 같이 작업할 수 없다. 그러나 Reader끼리는 같이 작업 가능하다.
💡 위 코드에서 어떤 이유로 Reader가 우선순위를 갖고있는걸까?
첫번째 Reader가 wsem에서 기다리고 있고 뒤에 Writer가 있는 상황일 때, 첫번째 Reader는 추가로 더 기다리고 있는 Reader가 있다면 Reader먼저 작업을 하게한다.
- readcount
- 몇 명이 동시에 데이터를 읽고 있는지 나타낸다.
- 첫 번째 Reader인지 파악하는데 사용된다.
- 여러 명의 Reader가 동시에 데이터를 읽으러 온 경우 모두가 wsem Critical Section에 들어갈 필요가 없다. 한 명이 들어갔을 때 다른 사람이 다 같이 들어갈 수 있기 때문이다.
- 따라서 첫 번째 Reader가 대표로 wsem에 줄을 선다 ⇒ 자신이 첫 번째 Reader인지 알기 위해 readcount 이용 =
if (readcount == 0) semWait(wsem)
- 공유변수 readcount를 둘 이상의 Reader가 동시에 연산하는 것을 막기 위해 Critical Section 내에서 작업한다.
- x 세마포 → readcount Critical Section
- Reader는 줄을 서는 위치가 두 개로 나눠진다.
- 한 사람(첫 번째 Reader)은 wsem에 줄을 선다.
- 나머지는 x에 줄을 선다.
- 첫 번째 Reader가
semWait(wsem)
을 빠져나오면semSignal(x)
를 통해 x의 문을 열어놓는다.- 마지막 Reader가 읽을 때까지 wsem에서 대기하는 Writer들은 Critical Section안에 진입할 수 없다.
- 마지막 Reader가
if (readcount == 0) semSignal(wsem)
을 실행해야 진입할 수 있다.- 즉, Reader의 경우 동시에 여러 명이 들어갈 수 있기 때문에 동시에 몇 명이 들어갔는지 세기 위해
readcount
변수를 사용한다.readcount
는 Critical Section 안에서 연산을 해야하기 때문에 별도의 x 세마포를 만든다. 첫 번째 Reader만 wsem에 줄을 서고 마지막 Reader만 다음 Writer한테 Signal을 준다.