TIL

[TIL/혼공컴운] 2025/02/24

프로세스 동기화 ✍️워드 프로세서에는 1)사용자로부터 입력을 받는 프로세스, 2)입력한 내용의 맞춤법을 검사하는 프로세스, 3)입력한 내용을 화면에 출력해 주는 프로세스 등이 있겠다. 이러한 프로세스들은 각기 다른 프로세스이지만 공동의 목표를 위해 서로 협력한다. 협력

2025년 2월 24일5min read

프로세스 동기화 ✍️

1. 동기화란? 🌿

1-1. 동기화의 의미 ⚙️

워드 프로세서에는 1)사용자로부터 입력을 받는 프로세스, 2)입력한 내용의 맞춤법을 검사하는 프로세스, 3)입력한 내용을 화면에 출력해 주는 프로세스 등이 있겠다.

이러한 프로세스들은 각기 다른 프로세스이지만 공동의 목표를 위해 서로 협력한다. 협력적으로 실행되는 프로세스들을 아무렇게나 동시에 실행해서는 안 될 테고, 따라서 올바른 실행을 위해 ``동기화(synchronization)``가 필수다.

프로세스 관점에서 동기화(synchronization)란, 프로세스들 사이의 수행 시기를 맞추는 것을 의미하고, 수행 시기를 맞추는 것에는 ``실행 순서 제어``상호 배제``가 있다.

🚨Pitfall 실행의 흐름을 갖는 모든 것은 동기화의 대상이기에 스레드도 동기화 대상이다. 다만 대부분의 전공서는 '프로세스 동기화'라고 칭한다.

#### 1-1-1. 실행 순서 제어를 위한 동기화 ✅

실행 순서 제어```란, 프로세스를 올바른 순서대로 실행하는 것을 의미한다.

![](https://velog.velcdn.com/images/minkwan/post/a6a3fe31-07c4-4562-9e2f-7ae1fabb3da2/image.png)

Writer 프로세스가 Book.txt 파일에 어떤 값을 저장하기도 전에, Reader 프로세스가 Book.txt 파일을 읽는 것은 올바른 실행 순서라고 할 수 없다.

위 예시로 보면, ```Writer 프로세스 -> Reader 프로세스```라는 프로세스 실행 순서를 보장하는 것이 ```실행 순서 제어```에 해당한다.

#### 1-1-2. 상호 배제를 위한 동기화 ✅

10만 원이 이미 저축되어 있었고, 프로세스 A와 B가 동시에 실행되었다면, 실행 결과로 17만 원이 계좌에 남아 있어야 한다.

상호 배제를 위한 동기화를 진행하지 않은 경우, 최종 잔액이 15만 원이 된다. ``잔액``이라는 공유가 불가능한 자원을 동시에 사용해서, 최종 결과로 B에서 저장한 값(10만 원 + 5만 원)만이 반영된 것이다. 내 2만 원 내놓으셈!

프로세스 A에서 '잔액'이라는 요소에 최종적으로 값을 반영하기 전까지, B가 잔액에 접근하지 못하게 하면, 17만 원으로 정상적으로 반영된다는 것을 확인할 수 있다.

1-2. 생산자와 소비자 문제 ⚙️

생산자와 소비자 문제는 ``상호 배제를 위한 동기화``에 관한 고전적 논의다.

생산자와 소비자는 '총합'이라는 데이터를 공유하고 있고, 생산자는 버퍼에 데이터를 삽입한 후 변수를 1 증가시키며, 소비자는 버퍼에 데이터를 빼내고 변수를 1 감소시킨다.

생산자 함수를 100,000번, 소비자 함수도 100,000번 동시에 실행하면 총합 변수가 계속 10개로 머물러 있을 것으로 기대한다. 하지만 소비자는 생산자의 작업이 끝나기도 전에 총합을 수정했고, 생산자도 소비자의 작업이 끝나기도 전에 총합을 수정했기에 엉뚱한 결과가 나온다.

다음 링크를 참고하십시오~

https://github.com/kangtegong/self-learning-cs/blob/main/producer_consumer/producer_consumer.md

1-3. 공유 자원과 임계 구역 ⚙️

공유 자원(shared resource)```은 전역 변수가 될 수도, 파일이 될 수도, 입출력장치나 보조기억장치가 될 수도 있다. 프로세스들의 공동의 자원이 되는 순간 그렇다.

그리고 이러한 공유 자원에에 접근하는 코드 영역을 ```임계 구역(critical section)```이라고 한다.

![](https://velog.velcdn.com/images/minkwan/post/5339d591-2c70-4377-aa3d-840d3cb19d63/image.png)

두 개 이상의 프로세스가 임계 구역에 진입하고자 하면 둘 중 하나는 대기해야 한다.

임계 구역은 두 개 이상의 프로세스가 동시에 실행되면 안 되는 영역이지만, 잘못된 실행으로 인해 여러 프로세스가 동시다발적으로 임계 구역의 코드를 실행하여 문제가 발생하기도 하는데, 이를 ```레이스 컨디션(race condition)```이라고 한다. 

인간이 이해하는 언어를 컴퓨터가 이해하는 언어로 변경하는 ```컴파일``` 과정을 거친 뒤, 생성된 목적 파일을 실행 파일로 변환하는 작업을 ```링킹```이라고 하는데, 해당 작업에 레이스 컨디션이 발생하는 근본적인 이유가 있다.

![](https://velog.velcdn.com/images/minkwan/post/826e126e-dca5-4093-bb5f-4de87c6b22cd/image.png)

컴퓨터는 인간이 이해하는 고급 언어가 아닌, 저급 언어를 실행하기에, 여러 줄의 저급 언어로 변환된 고급 언어 한 줄을 실행하는 과정에서 Context Switching이 일어날 수 있다.

![](https://velog.velcdn.com/images/minkwan/post/55f60d8b-6c41-48e1-a182-f2a9e8ac01bc/image.png)

운영체제는 이러한 문제를 해결하기 위해 세 가지 원칙에 집중한다. ```상호 배제```, ```진행```, ```유한 대기```가 바로 그것이다. 위 원칙에 입각하여 어떻게 동기화를 이루는지 알아볼 것이다.

## 2. 동기화 기법 🌿

### 2-1. 뮤텍스 락 ⚙️

탈의실(=임계 구역)을 여러 손님들(=프로세스)이 이용하는 상황을 상상해 보자.

탈의실에 사람이 있는지 없는지, 즉 임계 구역에 들어가도 되는지를 확인하려면 자물쇠를 보면 된다. 자물쇠가 걸려 있으면 탈의실 안에 사람이 있다고 판단하고 기다릴 수 있다.

자물쇠 기능을 코드로 구현한 것이 ```뮤텍스 락(Mutex lock)```이다.

뮤텍스 락은 하나의 전역 변수와 두 개의 함수로 구현할 수 있다.

![](https://velog.velcdn.com/images/minkwan/post/00e6c631-e23d-422d-94b1-32c8197ca808/image.png)

1. lock을 획득할 수 없다면 무작정 기다린다.
2. lock을 획득할 수 있다면 임계 구역을 잠근 뒤 작업을 진행한다.
3. 임계 구역에서 나올 때에는 잠금을 해제한다.

위 과정을 통해 임계 구역을 보호할 수 있다.

acquire 함수가 지속적으로 lock을 확인하는 것을 알 수 있는데, 이러한 대기 방식을 ```바쁜 대기(busy wait)```라 부른다.

### 2-2. 세마포어 ⚙️

뮤텍스 락은 탈의실이 하나 있는 경우였다. 

![](https://velog.velcdn.com/images/minkwan/post/3398d0e8-6099-4dd0-9464-2448747a49d0/image.jpg)

뮤텍스 락을 공유 자원이 여러 개 있는 상황에서도 적용할 수 있도록, 일반화한 동기화 방식이라고 볼 수 있다.

세마포어 역시 하나의 전역 변수와 두 개의 함수로 구현할 수 있다.

탈의실에 틀어갈 수 있는 프로세스가 0개 이하라면 계속적으로 확인하고, 탈의실에 들어갈 수 있으면 임계 구역에 진입할 수 있는 프로세스(S)를 1 감소시키고 임계 구역에 들어간다. 임계 구역에서의 작업을 마치면 S를 1 증가시킨다. 다음과 같은 모습을 보일 수 있다.

뮤텍스 락이나 세마포어 모두, 공유 자원을 지속적으로 체크해야 하는 바쁜 대기가 지속되는데, 이는 CPU 주기를 낭비한다는 점에서 손해라고 할 수 있다.

code
wait(){
  S--
  if(S < 0){
    // add this process to Queue
    sleep()
  }
}

// 임계 구역

signal(){
  S++
  if(S <= 0){
    // remove a process p to Queue
    wakeup(p)
  }
}

사용할 수 있는 탈의실이 없는 경우 해당 프로세스 상태를 ``대기 상태`로 만들고, 그 프로세스의 PCB를 세마포어를 위한 `대기 큐`에 넣는다. 이후 다른 프로세스가 임계 구역에서의 작업을 끝내고 signal 함수를 호출하면, signal 함수는 대기 중인 프로세스를 대기 큐에서 제거하고, 프로세스 상태를 `준비 상태`로 변경한 뒤 `준비 큐``로 옮긴다. 계속 체크하는 수고를 하지 말라는 얘기다.

지금까지는 세마포어를 이용한 ``상호 배제를 위한 동기화`였다면, 아래의 이미지는 세마포어를 이용해 `프로세스의 순서를 제어하는 동기화``하는 방법이다.

변수 S를 0으로 두고 위와 같이 실행한다.

P1이 먼저 실행되면 P1이 임계 구역에 먼저 진입하는 것은 자명한 일이고, P2가 먼저 실행되더라도 P2는 wait 함수를 만나, P1이 임계 구역에 진입하게 된다. P1이 임계 구역의 실행을 끝내고 signal을 호출하면 그제야 P2가 임계 구역에 진입한다.

요컨대, ``P1이 먼저 실행되는 P2가 먼저 실행되든, 반드시 P1-P2 순서대로 실행된다``.

2-3. 모니터 ⚙️

세마포어의 단점에는 ``매번 일일이 wait와 signal 함수를 명시해야 하는 번거로움, 중복 사용, 사용 누락`` 등이 있다. 헷갈릴까 싶지만 코드가 방대해지고 복잡해지면 휴먼 에러는 무조건 발생하게 된다.