Thread in OS
- 프로세스란 현대와 과거에서 바라보는 것이 다르다. 이것이 달라지는 조건은 스케쥴러의 유무다.
- 과거에 CPU는 프로세스가 독점하고, 메모리 역시 프로세스가 자유롭게 사용할 수 있었다.
- 하지만 OS 스케쥴러가 등장하면서 CPU는 타임 슬라이스만큼 실행되고 멈추는 형태로 제어되게 된다. 또한 메모리 역시 가진 메모리를 다 사용하는 것이 아닌 OS에게 배당받는 공간만 사용받을 수 있게 된다.
- 프로그램은 하드디스크에 컴파일된 파일이 Code와 Dat 형태의 구조를 갖추고 섹션에 저장되어 있다.
- 실행되면 프로세스로 바뀌며 런타임에 OS에게 Stack, Heap 영역을 할당받아 Code, Data, Heap, Stack를 가지게 된다.
- CPU는 RAM 주소를 직접 액세스하는게 아니라 가상 메모리를 통해 접근한다. 이것은 페이징 테이블이라고 부르기도 한다.
- Q. taskStruct?
- 프로세스는 자신의 상태를 담기 위한 자료구조 PCB(Process Control Block)를 사용한다.
- 이를 리눅스 커널에서 구현한 것(자료구조)이 task_struct다.
- 각 태스크(프로세스)들은 커널 메모리에서 task_struct 구조체(PCB)로 표현된다.
- 이 구조체는 커널 메모리 영역 내에 존재한다.
- 스레드는 왜 써야 할까?
- 스레드 생성에는 태스크 구조체를 생성하고, 스택 영역을 배분하는 등 OS 자원들을 배분하지만 이건 프로세스를 통째로 생성하는 것보다 훨씬 가볍다.
- Heap 영역을 공유할 수 있어 성능적으로도 유리하다.
- 멀티 스레딩 환경에서 각 스레드들은 정해진 타임 슬라이스마다 CPU를 점유해 작업을 수행했다가, 타임 슬라이스가 끝나면 CPU를 놔주는 것을 반복하며 작업을 수행한다.
- 이를 컨텍스트 스위칭이라고 한다.
- 여기서 반납되는 스레드가 마지막 상태를 메모리에 저장하는 저장과 실행되는 스레드가 메모리에 저장된 상태를 복구하는 복원, 두 가지 과정으로 진행된다.
- 이 과정에서 CPU Register의 상태를 메모리 영역에 백업하고 복원하는 작업때문에 CPU 작업을 잠시 중지하게 되는 오버헤드가 발생한다.
- 컨텍스트 스위치에서 프로세스와 스레드의 차이는 얼마나 될까?
- 스레드는 힙 영역을 공유하기 때문에 CPU와 RAM 사이에서 캐싱을 위한 L1,L2,L3 메모리에 데이터를 남겨놓을 수 있다.
- 하지만 프로세스는 서로 다른 프로세스끼리는 데이터 공유가 없기 때문에 이 캐시를 사용할 수 없다. 따라서 캐싱을 이유로 멀티 프로세스보단 멀티 스레드가 더 나은 방법인 것이다.
Thread in JVM
- JVM → OS → HW
- 추상화 레벨이다. 여기서 추상화때문에 오버헤드가 발생한다.
- JVM은 OS의 Thread를 사용하는데 이를 랩핑하는 것과 가비지 컬렉션때문에 오버헤드가 발생한다.
- Java에서 Thread + Runnable를 통해 비동기 코드를 구현했을 때 결과값은 힙 영역에 공유되는 Map을 통해 받았었다.
- 하지만 둘의 컨텍스트가 다르기 때문에 만약 서브 스레드(새로 생성된 Thread 클래스)가 메인 스레드(main() 메서드)보다 늦게 끝나게 되면 main 메서드에서 Map을 확인했을 때 비어있게 된다.
- 따라서 Object 클래스의 wait(), notify()를 통해 이런 문제를 해결하고자 했다.
- notify() 를 만약 빼먹게 되면 실행 흐름이 블로킹되어 버그가 발생하는 경우가 많았다.
- 이를 극복하기 위해 등장하게 된 것이 Future클래스다.
- 하지만 Future도 단일 처리만 가능하시 때문에 태스크 상의 의존성이 존재하는 연속된 태스크 작업에는 불편함이 많았다.
- 이를 극복하기 위해 등장한 것이 CompletableFuture 클래스
- Futer<T>, CompletionState<T> 인터페이스로 구현
- 메서드 체이닝을 통해 태스크의 연속성을 해결
- 메서드 래퍼런스를 통한 가독성
- 하지만 이것도 역시 Single Value callback 형태이다. 태스크를 컬렉션에 넣고 CompletableFuture처럼 처리할 수 있을까?
- ThreadPoll이 꽉 찬 상황은 어떻게 해결할 수 있을까? NIO(Native, Nonblocking)?
- Native IO
- 기존에는 JVM 메모리 힙 영역에 카피 + 핸들링 과정이 필요했는데
- Native I/O는 이런 과정을 빼고 직접 접근할 수 있다.
- Nonblocking IO
- Selector ,channel, Handler
- 네트워크를 예로 들면 Selector에 클라이언트가 채널을 통해 소켓 통신을 요청
- Selector는 각 요청들을 받아서 Hanlder에게 전달
- Handler는 싱글 스레드로 들어오는 요청만 처리 후 반환
- Selector는 처리된 요청을 다시 클라이언트에게 반환
- Spring MVC에서는 DefferdResult
- 이벤트 프로그래밍
- 이벤트를 발생시키는 투수 Source, 야구공처럼 날아오는 Event, 날아오는 이벤트 야구공을 치는 Listener, Handler
- backpressure
- back pressure valve
- 처리할 수 없는 요청을 빠르게 버려버리는 fast fail, 물고 있는 것도 장애다.
Ref