티스토리 뷰
Process ⊃Thread
- 프로세스
- OS로부터 자원을 할당받는 작업의 단위
- 여기서 자원이란 파일, 메모리, 코드, 스택, 힙 등을 모두 포함
- 위와 같은 특성상(메모리 공유 X) 프로세스 사이의 Context Switching(정보 교환) 이 어려워 여러 여러 개의 프로세스를 가진 프로그램의 동시 작업 수행 어려움 → 소프트웨어의 복잡도가 증가할수록 발목을 잡는 요인
- 독립적인 작업이기 때문에, 한 프로세스의 강제종료가 다른 프로세스에 영향을 주지 않는다.
- 이와 같은 배경에서 탄생한, 프로세스보다 작은 단위가 바로 스레드
- 스레드
- 프로세스의 자원을 사용하는 실행의 최소 단위
- 독립 실행을 위해 각자의 PC Register, 스택을 가지며 프로세스 내부의 힙 메모리, 데이터, 코드는 공유
- 때문에 생성 및 처리속도가 빠르며, 스레드 간의 Context Switching이 쉬워 여러 작업의 동시 실행이 가능하다.
- 다만 공유자원에 대한 동시 접근 문제로 몇 가지 문제와 그에 따른 새로운 개념들이 등장하게 된다.
- 대표적으로 프로세스와 달리 한 프로세스 내의 특정 스레드에 오류가 발생하면 나머지도 같이 종료된다.
먼저 알고 넘어가야 할 단어를 정의하고 나머지 문제점과 그 해결방법에 대해 하나씩 살펴보자.
Interrupt & Context Switching
동시성과 병렬성 글에서 정리한 적 있지만, 하나의 CPU 코어는 실제로는 한 번에 하나의 일만을 처리하기 때문에,
여러 가지 일을 처리하는 것처럼 보이기 위해 여러 작업을 빠르게 번갈아가면서 수행한다.
이는 필연적으로 하나의 프로세스가 진행되는 동안 나머지가 중단되어 있기를 요구하는데,
다른 프로세스를 처리하기 위해 현재 실행 중인 프로세스를 중단하는 것을 Interrupt라고 한다.
추가로 interrupt는 명칭 그대로 예상하지 못한 에러나 외부의 개입이 발생했을 때 주로 발생한다.
계속해서 Interrupt 발생 시 이전까지의 작업 정보를 저장해야 할 필요가 생기는데,
중단된 프로세스의 정보(Context)를 PCB(Process Control Block)라는 프로세스 관리를 위한 일종의 자료구조에 올린 후
새로운 작업의 Context를 메모리에 올려 코어가 실행할 수 있도록 만들어주는 작업을 Context Switching이라고 한다.
정리하자면
- Interrupt는 예외적인 이벤트를 빠르게 처리하기 위한 기법이다.
- Context Switching은 하나의 코어가 여러 개의 프로세스를 다루는, 멀티 태스킹을 할 수 있도록 도와준다.
Synchronization
위에 적었듯이 프로세스 내부에 생성된 스레드는 힙 메모리와 데이터를 공유자원으로 사용한다.
이 때문에 필연적으로 발생할 수 있는 문제와 그에 따른 새로운 개념들이 있는데, 하나씩 알아보자.
- 서로 다른 스레드가 동일한 공유 변수에 접근해 연산 실행 → 결과가 매번 다르게 나옴
- 이처럼 여러 스레드가 하나의 공유 변수에 접근하는 상태를 경쟁 상태(Race Condition)라 한다.
- t1이 연산 결과를 메모리에 반영하기 전에 t2로 Context Switching이 일어나기 때문
요약하자면 스레드의 특성 때문에 데이터의 일관성이 깨지는 커다란 문제가 발생한다는 말인데,
이를 해결하기 위해 등장한 것이 동기화 개념이다.
참고로 기존에는 프로세스 동기화라는 용어를 사용했으나
현대 프로그래밍의 스케줄링은 대부분 스레드를 기준으로 하기 때문에
보통 동기화라고 하면 스레드 동기화를 가리킨다.
동기화는 한 마디로 정의하면 임계 영역(Critical Section)에 대한 경쟁 상태 발생 시 스레드의 접근 순서를 정해주는 것이다.
여기서 임계 영역이란 간단하게 말해 공유 자원이 존재하는 코드블록,
즉 위에서 봤듯이 접근 순서에 따라 데이터 일관성이 깨지는 영역이며
동기화의 목적은 당연하게도 다중 스레드 환경에서 임계 영역을 보호, 데이터 일관성을 확보하는 데 있다.
이를 많이 들어본 말로 Thread-Safe라고 부르기도 하며,
임계 영역 보호의 요구조건은 다음의 세 가지로, 일부가 아닌 모두를 만족해야만 한다.
- 상호 배제(Mutual Exclusion) - 임계 영역에는 한 번에 하나의 스레드만 접근할 수 있다. 즉, 다른 스레드는 배제된다.
많이 들어본 락(Lock)이 여기에서 등장하는 용어이다. - 진행(Progress) - 임계 영역에 접근한 스레드가 없다면 다른 스레드가 접근할 수 있도록 진행해야 한다.
만약 여러 개의 스레드가 접근하려 한다면 순서를 결정하는 것은 유한한 시간 안에 이루어져야 한다. - 한정 대기(Bounded Waiting) - 대기중인 모든 스레드는 유한한 시간 안에 임계 영역에 접근할 수 있어야 한다.
- 진행과 한정 대기의 차이
- 진행 - 임계 영역에 대한 대기 스레드의 접근을 멈추지 않는 것이 목적
- 한정 대기 - 대기 스레드의 기아현상이 나타나지 않도록 실행 중인 프로세스를 제한하는 것이 목적
- 기아(Starvation) - 굶주린다 할때 그 기아이다. 스레드가 무한정 대기하는 현상을 가리킨다.
계속해서 동기화를 실현하기 위한 방법을 알아보자.
Spin Lock
임계 영역에 락이 걸려 접근이 불가능할 때, 가능해질 때까지 루프를 돌며 재시도하는 상태를 말한다.
CPU를 점유하며 무의미한 루프를 돌기 때문에 Busy-Waiting이며, 해당 스레드로는 Context Switching이 일어나지 않는다.
또한 상태가 획득(Lock)과 해제(Unlock)만 존재한다는 특성을 바탕으로 한 번에 단 하나의 스레드의 접근만을 허용한다.
이는 대기 시간이 짧은 경우엔 유리하게 작용하지만, 그렇지 않은 경우 자원을 차지해 오버헤드가 발생한다는 단점이 있다.
추가로 위와 같은 특성 때문에 스핀락은 멀티코어 시스템에서만 사용 가능하며, 락의 획득과 해제의 주체는 동일해야 한다.
*오버헤드 - 특정 처리, 혹은 기능을 위해 (추가로) 들어가는 시간과 자원. 보통 낭비되는 경우를 가리킨다.
Mutex Lock
뮤텍스는 위에서 언급한 상호 배제(Mutual Exclusion)의 축약어이다.
획득/해제의 상태만 존재한다는 특징은 스핀락과 공유하지만, 언락 상태에서 루프를 도는 스핀락과는 달리
뮤텍스는 앞의 스레드에게 자신을 깨워달라고 요청 후 Sleep 상태로 들어간다.
정확하게는 큐를 사용해 대기줄을 만들고, 실행중인 스레드가 락을 반환하면서 다음 스레드를 깨우고,
깨어난 스레드가 권한 획득을 시도하는 방식이다.
추가로 뮤텍스는 코어의 개수과 상관없이 사용할 수 있다. 즉 해당 코어에서 다른 작업을 동시에 진행할 수 있다.
그러나 다음 스레드를 깨우는 과정에서 Context Switching이 발생하며, 이는 (주로 CPU의)오버헤드를 발생시킨다.
Spin Lock vs. Mutex Lock
- 공통점 - 임계 영역의 락이 풀릴 때까지 대기하는 방식
- 차이점
- Spin Lock - 대기중 루프를 돌며 CPU 점유(양보 불가). Context Switching이 없음. 멀티 코어에서 사용 가능
- Mutex Lock - 큐에서 Sleep 상태로 들어가 대기하며(CPU 양보 가능), Context Switching 수행. 코어 상관없음
- 유리한 경우
- Spin Lock - 멀티 코어 환경이면서 임계 영역의 작업이 Context Switchcing보다 빨리 끝나는 경우
- Mutex Lock - 그 외
Semaphore
세마포어는 한 마디로 말하면 일반화된 Mutex Lock이라고 볼 수 있다.
획득/해제(0, 1)의 상태만 존재하는 뮤텍스와 달리 세마포어는 0 이상의 정수 값(S 변수)을 가질 수 있기 때문에
하나 이상의 스레드가 공유자원에 접근하는 것을 허용할 가능성이 있다.
그러니까 어떻게 보자면 상호 배제 원칙을 제한적으로 위반하는 것이라고 할 수도 있다.
또한 권한의 획득과 해제의 주체가 같은 뮤텍스와는 다르게 권한을 부여하는 P연산과 회수하는 V연산으로 나뉘며,
각 스레드는 임계 영역에 진입하기 전 S의 상태를 통해 접근 가능 여부를 확인한다.
추가로 P/V연산은 다른 스레드가 동일한 S에 대해 동시에 수행할 수 없도록 원자성이 보장되어 있고,
준비 상태의 스레드는 마찬가지로 큐로 들어간다.
계속해서 세마포어의 진행 과정을 요약하자.
- 현재 S = 2라고 가정
- 특정 스레드가 임계영역에 접근
- S = 2 ↔ 현재 접근 가능한 스레드의 개수(=접근 가능한 공유자원의 개수)가 2이므로
- 대기하지 않고 바로 작업으로 들어가며 P연산(S--;) 수행 (S = 1)
- 임계영역 내의 임의의 스레드가 작업을 마치며 V연산(S++;) 수행 (S = 2)
- 여기서 P연산과 V연산의 주체는 같을 필요가 없다.
- P와 V 사이에는 짧은 Busy Waiting이 발생한다.
- V연산에는 대기 중인 다음 스레드를 깨우는 역할도 포함
- 만약 스레드 접근 시 S = 0이라면(P연산의 결과가 음수라면) 대기모드로 진입
또한 세마포어는 아래와 같이 크게 두 종류로 나눌 수 있다.
- 이진 세마포어(Binary Semaphore)
- S의 값을 0 또는 1만 허용하는 세마포어, 즉 공유 자원이 하나뿐이다.
- 뮤텍스 역시 이진 세마포어의 일종이지만, 연산의 주체가 달라질 수 있다는 차이점이 존재한다.
또한 뮤텍스는 우선순위 상속(Priority Inheritance) 방식을 지니고 있어 락 획득에 실패해도 우선순위가 유지된다.
- 카운팅 세마포어(Counting Semaphore)
- 위에서 설명한 일반적인 방식의 세마포어, 공유 자원이 2 이상일 수 있다.
- 스레드의 접근 순서를 관리하며, 공유자원과 스레드 수에 따라 보다 유연한 제어가 가능하다
마지막으로 세마포어의 단점은 P/V 연산자의 순서가 바뀌거나 하나라도 생략되었을 경우 교착상태가 발생하며,
이 연산이 프로그램 전체에 퍼져있을 경우 문제 지점을 파악하기가 어렵다.
또한 저수준 언어로 이루어져있기 때문에 다루기가 어렵고 개발자의 실수 확률이 높다.
Mutex Lock vs. Semaphore
Mutex Lock | Semaphore | |
공통점 | 다중 스레드 사용을 위한 동기화 기법. 여러 스레드가 임계영역에 동시에 접근하는 경우의 제어기술 | |
차이점 | 공유자원에 하나의 스레드만 접근 가능 락/해제 사용. 상태가 0과 1인 이진 세마포어의 일종 락/해제의 주체가 같아야 함(락을 소유하는 것이 가능) 프로세스 종료와 함께 Clean Up (자바에선 좀 다름) 상호 배제만 필요한 경우 권장 |
공유자원에 여러 스레드가 접근 가능 P/V연산 사용. S를 정수로 관리 P/V 연산의 주체가 달라도 됨(소유 불가능) 시스템상 파일의 형태로 존재 접근 가능한 공유자원이 두 개 이상일 경우 권장 |
Monitor
다음 글에 다루겠지만 세마포어의 가장 큰 단점 중의 하나는 모든 교착상태를 해결하지 못한다는 점이다.
또한 뮤텍스와 세마포어 모두 저수준 언어(어셈블리어)로 만들어져 있어 사용하기 어려운 데다
개발자가 실수하기 쉽고, 완벽한 상호 배제 역시 구현하기 힘들다.
모니터는 이 부분을 극복하면서도 사용하기 편한 고수준 언어로 구성되어 등장했는데, 자바 역시 언어 차원에서 이를 지원한다.
공유 자원을 내부에 숨기고 이에 접근하기 위한 인터페이스만을 제공하는데,
예를 들자면 동기화에 흔히 사용해온 synchronized, wait(), notify()등의 함수 역시 캡슐화된 모니터의 키워드이다.
(자바뿐 아니라 C#, Go, 파이썬, 루비 등에서도 지원)
모니터는 이진 세마포어를 기반으로 만들어진 상호 배제 프로그램이며, 공유자원과 두 개의 큐로 이루어져 있다.
- 상호 배제 큐(Mutual Exclusion Queue) - 상호 배제를 위해 한 번에 하나씩만 공유자원에 들어가기 위한 큐
- 조건 동기 큐(Conditional Synchronization Queue) - 락 해제를 기다리는, wait()이 적용된 스레드가 대기하는 큐
모니터는 뮤텍스나 세마포어와는 달리 락의 획득과 해제를 모두 자동으로 처리해 준다. 물론 개입하는 방법도 존재한다.
뿐만 아니라 가볍고 빠르기까지 해서 각종 언어의 자동 동기화 도구로 사용된다.
굳이 단점을 뽑자면 컴파일 단계에서 구현되기 때문에 컴파일러에 부담이 간다.
'Development > Technical Interview' 카테고리의 다른 글
[면접 준비 - Network]HTTP Request 횟수에 대하여 (2) | 2022.12.26 |
---|---|
[면접 준비 - CS]멀티 프로세스, 멀티 스레드, 싱글 스레드, 그리고 (4) | 2022.12.24 |
[면접 준비 - CS]Deadlock의 원인과 각종 대처 방법 (3) | 2022.12.23 |
[면접 준비 - CS]노드(Node)에 대하여 (2) | 2022.12.20 |
[면접 준비 - Network]부하 분산(SLB), AWS ELB에 관하여 (3) | 2022.12.19 |
[면접 준비 - Java]Data Structures in a Nutshell (4) | 2022.12.17 |
- Total
- Today
- Yesterday
- 스트림
- 스프링
- 리스트
- java
- 맛집
- 면접 준비
- Python
- 파이썬
- 알고리즘
- 칼이사
- 유럽
- 동적계획법
- 백준
- 유럽여행
- 여행
- 자바
- Algorithm
- 세계여행
- RX100M5
- 중남미
- BOJ
- 기술면접
- Backjoon
- 야경
- a6000
- 세모
- 남미
- 세계일주
- spring
- 지지
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 31 |