Synchronization(동기화) 문제란, 데이터를 읽어오기만 하면 되지만, 읽은 후 연산하고 저장할 때 누가 언제 읽었냐에 따라 결과가 달라지는 상황을 의미합니다. 이로 인해 데이터의 일관성이 깨질 수 있습니다.
🤼♂️ Process Synchronization
공유 데이터(shared data)에 대한 동시 접근(concurrent access)은 데이터 불일치 문제(inconsistency)를 발생시킬 수 있습니다. 따라서 일관성을 유지하기 위해서는 협력 프로세스(cooperating process) 간의 실행 순서를 정해주는 메커니즘이 필요합니다.
Race condition이란 여러 프로세스나 스레드가 동시에 같은 데이터를 조작할 때 타이밍이나 접근 순서에 따라 결과가 달라질 수 있는 상황을 의미합니다. 이를 방지하기 위해 concurrent process는 동기화(synchronize)되어야 합니다. 예를 들어, count++ 연산 중 count--가 연산 완료 이전 자료를 가져가서 작업하고 저장하면 --만 저장될 수 있습니다.
Kernel 수행 중 인터럽트 발생: 이를 방지하기 위해 running 중에는 끝날 때까지 인터럽트를 비활성화합니다.
Process가 system call을 하여 kernel mode로 수행 중 context switch가 발생하는 경우: 커널 모드에서 수행 중일 때는 CPU를 선점하지 않습니다. 커널 모드에서 유저 모드로 돌아갈 때 선점합니다.
Multiprocessor에서 shared memory 내의 kernel data에 접근할 때: 커널 전체를 lock/unlock하거나, 공유 데이터에 대한 lock을 설정합니다.
Synchronization이란 여러 프로세스나 스레드가 동시에 실행되더라도 공유 데이터의 일관성을 유지하는 것입니다.
Critical section은 공유 데이터의 일관성을 보장하기 위해 하나의 프로세스나 스레드만 진입해서 실행(mutual exclusion)할 수 있는 영역입니다.
아래 조건을 모두 만족해야 합니다.
Mutual Exclusion (상호 배제): 한 번에 하나의 프로세스나 스레드만 critical section에서 실행될 수 있습니다.
Progress (진행): 아무도 critical section에 없으면, 들어가고자 하는 프로세스가 있다면 진입할 수 있어야 합니다.
Bounded Waiting (한정된 대기): 하나의 프로세스나 스레드가 critical section에 들어가기 위해 무한정 기다리는 상황이 발생해서는 안 됩니다. 이는 다른 프로세스들의 기아(Starvation)를 막기 위해서입니다.
임계영역에 진입할 수 있을 때까지 반복해서 시도합니다.
임계영역이 lock 상태일 때 진입이 가능해질 때까지 루프를 돌며 재시도하여 스레드가 CPU를 점유한 상태입니다.
단점
기다리는 동안 CPU를 낭비하게 됩니다. (Busy-Waiting 상태)
TestAndSet 함수를 통해 T1, T2가 동시에 실행하지 않도록 합니다.
여러 스레드를 실행하는 환경에서 자원에 대한 접근을 제한하기 위한 동기화 메커니즘입니다.
lock을 가질 수 있을 때까지 휴식합니다.
Mutual Exclusion(상호 배제)의 약자입니다.
[스핀락과의 차이점]
스핀락이 임계영역이 unlock될 때까지 Busy-Waiting 상태를 유지한다면, 뮤텍스는 Block 상태로 들어갔다가 Wakeup 후 다시 권한 획득을 시도합니다.
뮤텍스는 CPU 사이클 낭비를 최소화합니다.
뮤텍스는 value와 guard로 구성됩니다:
Value: 0, 1인데, 1이 되면 임계영역 입장이 가능합니다.
Guard: race condition 발생을 막기 위해 보호하는 장치입니다.
뮤텍스가 항상 스핀락보다 좋은 것은 아닙니다. 뮤텍스는 컨텍스트 스위칭이 발생하기 때문에, 임계영역에서의 수행 작업이 빠를 경우 스핀락이 더 효율적일 수 있습니다.
세마포어는 멀티프로그래밍 환경에서 다수의 프로세스나 스레드가 여러 개의 공유 자원에 대한 접근을 제한하는 방법입니다.
Signal Mechanism: 하나 이상의 프로세스나 스레드가 critical section에 접근 가능하도록 합니다.
목적: Mutual Exclusion이 아닌 공유 자원 관리입니다.
세마포어는 정수형으로 표현되며, value가 0, 1, 2, ...가 될 수 있습니다. 이를 통해 하나 이상의 컴포넌트가 공유 자원에 접근할 수 있도록 합니다.
semaphore -> wait(); : 임계 구역에 진입할 때 호출합니다.
semaphore -> signal(); : 임계 구역에서 나올 때 호출합니다.
Binary Semaphore: 뮤텍스와 동일하게 value가 1인 세마포어입니다.
Counting Semaphore: value 값이 1보다 큰 세마포어입니다.
동기화 문제를 해결하기 위해서는 공유 데이터의 일관성을 보장할 수 있는 다양한 메커니즘이 필요합니다. 각각의 동기화 기법은 상황에 따라 장단점이 있으며, 적절한 방법을 선택하여 적용하는 것이 중요합니다.
식당을 예시로 생각해 보자.
🍽️ 식당: 임계 영역
🧍♀️ 손님: 프로세스/스레드
🔒 가게 문 잠금: lock
💤 대기실: 큐
👩🍳 직원: 코어
가게가 작아서 좌석이 하나뿐이다. 그래서 한 명씩 입장해서 식사를 할 수 있다.
이때 가게로 손님1 주연이 가게로 들어온다.
🙋♂️🙆♂️ 손님1 (주연) : 떡볶이 1인분이요~
자리가 꽉 찼기 때문에 직원은 가게 문을 lock 잠근다.
이어서 맛집 소문이 났는지 손님2 지흔이 가게 앞으로 온다.
🙋♀️🙅♀️ 손님2 (지흔) : 엇 ㅠㅠ 꽉 찼네?
이때 지흔이 대기하는 방법은 두 가지가 있다.
🌀 1) 자리가 생길 때까지 계속 문을 두드린다. -> spinlock (스핀락)
배고픈 지흔.. 계속 문을 두드린다..!! 자리 없어요??? 자리 없어요???
💡 Busy-Waiting 상태
계속 문을 두드리는 지흔.. 직원은 계속 대답해야 해서 다른 설거지 등 업무를 할 수 없다. 효율적이지 않아 ㅠㅠ
그래도 이 방식이 빠른 경우가 있다.
대기실로 가는 시간(=컨텍스트 스위칭)보다 손님의 식사 속도가 더 빠른 경우
직원이 여러 명(=멀티 코어) 있는 식당인 경우
💤 2) 대기실에서 기다리다가 부르면 들어간다. -> 큐에 Block-Wakeup
계속 기다리다가 먼저 온 손님부터 차례로 입장한다.
💡 뮤텍스(Mutex), Block-Wakeup, Blocking-lock
떡볶이를 다 먹은 주연.
🙋♂️ 손님1 (주연) : 잘 먹었습니다~
손님이 나가기 때문에 문을 연다. unlock
🙋♀️ 손님2 (지흔) : 이야~~ 자리가 생겼다. 들어가자~
드디어 지흔 입장한다.
스핀락과 뮤택스와 달리 표현형이 정수형이다. 아래 value가 0,1,2, .... 가능하다.
이 점을 살려 하나 이상의 컴포넌트가 공유자원에 접근할 수 있도록 허용할 수 있다.
식당이 '또간집' 프로그램에 소개되었다. sns 맛집이 된 식당. 리모델링으로 3자리로 늘렸다.
문 앞에 남은 좌석 3으로 걸려있다.
손님 3명이 입장한다.
이때 남은 좌석을 정확하게 표시하기 위해 식당에 규칙이 생겼다. 일명 PV(wait, signal) 규칙!
입장하는 손님은 P를 외치면서 들어온다.
🙋♂️ 손님1 (주연), 🙋♀️손님2 (지흔), 🙋 손님3 (문혜) : 하잉 PPP~
남은 좌석은 0이 된다.
이때 뒤늦게 등장한 손님4 지은.
🤦 손님4 지은 : 아~~ 아쉽다. 지은 역시 계속 문을 두드릴 수도 있고 대기실에서 기다릴 수도 있다.
지은이 대기실로 가는 순간 남은 좌석은 -1가 된다.
먹는 속도가 빠른 문혜! 먼저 온 주연, 지흔보다 먼저 식사를 마치고 식당을 빠져나간다.
🙋 손님3 (문혜) : V~~
이때 나가면서 대기실을 보니 지은이 있다. 지은을 깨운다.
현대의 프로그래밍 환경에서는 멀티프로세스와 멀티스레드 환경이 기본이며, 동기화 문제 해결을 위해 다양한 도구들이 함께 사용됩니다. 특히, 뮤텍스와 세마포어는 여전히 널리 사용되지만, 문제의 성격과 특성에 따라 다른 도구들도 선택될 수 있습니다.
최근에는 하드웨어의 발전과 함께 원자적 연산이나 락프리 기법이 중요성을 더해가고 있습니다. 이는 멀티코어 시스템에서 병렬 처리를 최적화하고 데드락을 피하기 위한 노력의 일환으로 볼 수 있습니다.
따라서, 멀티프로세스와 멀티스레드 환경에서는 다양한 동기화 도구를 적절히 조합하여 성능을 최적화하고 코드의 안정성을 확보하는 것이 중요합니다.
참고자료 : https://github.com/devSquad-study/2023-CS-Study/blob/main/OS/os_process_synchronization.md