프로세스 동기화
경쟁 상태(race condition)
여러 프로세스 또는 스레드에서 공유 자원에 동시에 접근해 경쟁하는 상태
여러 프로세스 또는 스레드에서 하나의 공유 자원에 접근하는 경우, 데이터베이스에서 여러 트랜잭션이 동일한 레코드에 접근할 때 자원에 접근하는 순서에 따라 결과 값이 달라질 수 있다.
경쟁 상태 문제 Case 1 - 무결성 위반
통장 잔고를 예시로 들어보자. 위 그림 처럼 2개의 쓰레드와 잔고라는 공유 자원이 존재한다. 각 쓰레드는 출금 작업을 동시에 실행하고 있다. 단, 잔고가 출금 금액보다 적을때에는 출금할 수 없다.
쓰레드1이 5000원 출금 작업을 수행하기 위해 잔액을 먼저 조회한뒤, 조건문으로 잔액이 5000원 이상인지 판단했다. 그리고 출금을 해야하는데, Context Switch가 발생하여, 프로그램의 실행 흐름이 쓰레드2로 교체되었다.
쓰레드2도 마찬가지로 쓰레드1과 동일한 작업을 한다. 다행히 쓰레드2에서는 출금 전에 Context Switch가 발생하지 않아 출금 작업을 마쳤다. 잔액은 0원이 되었다.
이후 Context Switch가 발생하여, 실행 흐름이 쓰레드1로 교체되었다. 쓰레드1은 방금 전 끊긴 작업을 이어서 수행해야한다. 쓰레드1 입장에서는 방금 전 잔액이 출금 금액 이상이라는 것을 확인했으므로 잔액에서 5000원을 빼면 된다.
이때 문제가 발생한다. 잔액은 아까 쓰레드2의 작업으로 이미 0원이 되었다. 이 상태에서 쓰레드1이 5000원을 잔액에서 뺀다면, 잔액은 0보다 작은 수가 된다. 무결성이 깨진 것이다.
데이터 무결성 : 데이터 값이 정확한 상태를 의미한다.
경쟁 상태 문제 Case 2 - 정합성 위반
이번에는 쓰레드1과 쓰레드2가 같은 잔고에 입금 작업을 수행하려 한다. 쓰레드1은 입금을 위해 기존의 잔액을 조회하고, 지역 변수에 저장해둔다. 이후 Context Switch가 발생하여, 쓰레드2로 실행 흐름이 넘어간다.
쓰레드2에서도 마찬가지로 잔액을 조회하고, 기존 잔액이라는 지역변수에 저장한다. 이후에 잔액을 기존 잔액 + 10000
로 대입한다. 잔액은 10,000만원이 늘어난 15,000원이 되었다. 이후 쓰레드1로 Context Switch된다.
쓰레드1은 아까 기존 잔액이라는 지역 변수에 저장해둔 5,000원이라는 값을 꺼내 10,000을 더한 15,000이라는 값을 대입하게 된다. 분명 10,000원을 2번 입금해서 잔액은 25,000원이 되어야 하는데 결과적으로 15,000인 것을 확인할 수 있다. 입금 내역을 확인하면 10,000원이 2번 입금되었을 텐데 잔액은 그렇지 않다. 정합성이 깨진것이다.
데이터 정합성 : 어떤 데이터들이 값이 서로 일치하는 상태를 의미한다.
임계 영역(critical section)
공유 자원에 접근할 수 있고 접근 순서에 따라 결과가 달라지는 코드 영역
프로세스 동기화(process synchronization)
공유 자원에 동시에 접근하지 못하도록 접근 순서를 제어
공유 자원에 대하여 다수 프로세스 혹은 쓰레드가 접근할 때 경쟁 상태가 발생하여 데이터의 일관성이 훼손될 수 있다.
이를 방지하여 여러 프로세스가 공유 자원에 접근해도 데이터의 일관성을 유지시키기 위한 방법은 3가지가 있다.
- 상호배제 기법(mutual exclusive) : 어떤 프로세스가 임계 영역을 실행 중일 때 다른 프로세스가 임계 영역에 접근할 수 없다. 상호배제 기법으로는 뮤텍스와 세마포어가 있다.
- 진행(progress) : 임계 영역을 실행 중인 프로세스가 없을 때 다른 프로세스가 임계 영역을 실행한다.
- 한정된 대기(bounded waiting) : 임계 영역에 접근을 요청했을 때 무한한 시간을 기다리지 않는다.
뮤텍스
락(lock)을 가진 하나의 프로세스만이 공유 자원에 접근할 수 있게 하는 방법
임계구역(Critical Section)을 가진 스레드들의 실행시간(Running Time)이 서로 겹치지 않고 각각 단독으로 실행(상호배제_Mutual Exclusion)되도록 하는 기술입니다.
한 프로세스에 의해 소유될 수 있는 Key를 기반으로 한 상호배제 기법이고 Key에 해당하는 어떤 객체(Object)가 있으며, 이 객체를 소유한 스레드/프로세스만이 공유자원에 접근할 수 있습니다.
- 화장실 한 칸과 화장실 열쇠 1개가 있다.
- A 가 열쇠를 갖고 화장실을 사용한다.
- B는 열쇠가 없어 기다린다.
- A가 사용 후 키를 반납하면 기다리던 B가 열쇠를 갖고 화장실을 사용한다.
화장실 = 공유 자원을 포함한 임계 영역, 열쇠 = 락, A와 B는 공유 자원에 접근하려는 프로세스를 의미한다. 임계 영역에 먼저 접근한 프로세스가 임계 영역에 락을 걸면 다른 프로세스들은 해당 프로세스가 락을 해제할 때까지 대기해야 한다. 이처럼 임계 영역에 접근한 프로세스가 임계 영역에 락을 건다고 해서 락킹 매커니즘이라고도 한다.
임계 영역에 접근하지 못한 프로세스는 락을 얻기 위해 기다리는 동안 락이 풀렸는지 반복문을 돌며 확인한다. 이를 바쁜 대기(busy waiting)의 한 종류인 스핀락(spinlock)이라고 한다. 프로세스가 대기 상태가 되지 않고 반복문을 돌며 자원 사용 가능 여부를 확인하므로 프로세스가 빠르게 교체될 수 있다.
바쁜 대기(busy waiting) : 프로세스가 공유 자원에 접근할 수 있는 권한을 얻을 때까지 확인하는 과정
세마포어 (semaphore)
공유 자원에 접근할 수 있는 프로세스의 수를 정해 접근을 제어하는 방법
임계 영역에 접근할 수 있는 프로세스 혹은 쓰레드의 허용 최대치를 N개로 지정하고 그 수를 넘지 않도록 하는 것이다.
프로세스는 이 공유 자원에 접근하면, 정수값 하나를 줄인다. 공유 자원을 모두 사용하고 임계 구역에서 나온 프로세스는 다시 정수값을 하나 늘린다. 각각의 과정을 wait()
(또는 P), signal()
(또는 V) 라고 한다. 더 자세히 알아보자.
프로세스가 공유 자원에 접근할 때는 wait()
함수를 실행한다. wait()
함수는 세마포어 정수의 값을 1 감소시킨다. 만약 감소된 세마포어 정수가 음수라면, 해당 프로세스는 세마포어 대기열에서 기다려야 한다.
프로세스가 공유 자원을 모두 사용하고 임계 구역에서 벗어날 때 signal()
함수를 실행한다. signal()
함수를 실행하면 세마포어 정수를 1 증가시키고, 세마포어 대기열에서 기다리고 있는 맨 앞의 프로세스 하나를 깨워 공유 자원을 허용케한다.
이 세마포어 정수의 크기에 따라 바이너리 세마포어(binary semaphore)와 계수 세마포어(counting semaphore)로 구분된다. 바이너리 세마포어는 세마포어 정수를 0과 1로만 가질 수 있다. 바이너리 세마포어는 뮤텍스와 비슷하게 동작한다. 단, 뮤텍스는 잠금 메커니즘이고, 세마포어는 신호 기반의 상호 배제 방법이다. 계수 세마포어는 1보다 큰 세마포어 정수를 가질 수 있는 방식을 의미한다.
모니터 (monitor)
세마포어의 상호 배제 로직을 추상화하고 공유 자원 접근에 대한 인터페이스만을 제공하는 고수준의 세마포어
세마포어를 사용하기 위해서는 임계 구역에 명시적으로 상호 배제 로직을 구현해야한다. 모니터는 이 상호 배제 로직을 추상화하고, 공유 자원 접근에 대한 인터페이스만을 제공한다. 또한 공유 자원도 외부로부터 캡슐화하여 숨긴다. 세마포어에 비해 좀 더 고수준이라고 할 수 있다. 따라서 모니터를 사용하면 상호 배제를 세마포어에 비해 구현하기 쉬우며 실수가 줄어든다. 모니터는 주로 고급 프로그래밍 언어에서 제공하는 방식이다. 자바의 synchronized
키워드도 모니터를 사용하여 구현되었다.
공유 자원에 접근하고자 하는 프로세스/쓰레드는 모니터안으로 들어가야한다. 모니터는 모니터큐에 작업을 순차적으로 쌓아두고, 한번에 한 프로세스/쓰레드만 임계 영역에 접근할 수 있도록 한다. 즉, 한번에 하나의 프로세스만 모니터에서 활동할 수 있도록 보장한다. 개념적으로는 이진 세마포어의 기능을 제공하는 것이다.
스레드 안전(Thread safe)
멀티 스레드 환경에서 프로세스 동기화가 이루어져 하나의 변수, 함수, 객체에 스레드 여러 개가 동시에 접근해도 문제가 없음
스레드 안전을 위한 조건
- 상호 배제(mutual exclusive) : 공유 자원에 접근해야 할 때 뮤텍스나 세마포어와 같은 상호배제 기법을 사용해 접근을 통제해야 한다.
- 원자 연산(atomic operation) : 공유 자원에 접근할 때 원자 연산을 이용하거나 원자적으로 정의된 연산을 이용해 연산 도중에 다른 스레드가 접근할 수 없게 한다. 원자 연산이란 '연산했다', '연산 안 했다' 두 가지만 존재하는 연산이다. 바이너리 세마포어의 개념과 비슷하다.
- 재진입성(reentrancy) : 특정 함수를 하나의 스레드에서 실행 중일 때 다른 스레드가 해당 함수를 실행해도 각 스레드에 올바른 결과가 나올 수 있게 해야 한다.
- 스레드 지역 저장소(thread local storage) : 각 스레드에서만 접근할 수 있는 저장소를 사용해서 공유되는 자원을 줄여야 한다.
Reference
'CS > 운영체제' 카테고리의 다른 글
7.IPC (0) | 2024.07.12 |
---|---|
6.교착 상태 (0) | 2024.07.12 |
4. 멀티 프로세스와 멀티 스레드 (0) | 2024.07.12 |
3. 멀티 태스킹과 PCB와 컨텍스트 스위칭 (0) | 2024.07.12 |
2. 프로그램과 프로세스, 스레드 (0) | 2024.07.12 |