1. Thread의 정의와 만들어진 배경
스레드(Thread)란, 프로세스(Process) 보다 작은 프로그램의 실행 단위이다.
컴퓨터가 발달하면서 하나의 프로그램에서 복잡한 동시 작업의 필요성이 제기됐다.
이에 따라서 하나의 프로그램에 여러 개의 프로세스가 필요했지만,
프로세스 간에는 자원의 공유가 어려웠다. 왜냐하면 서로 다른 프로세스는 완벽히 독립적인 메모리가 할당되기 때문이다.
CPU가 프로세스의 작업 중에 다른 프로세스에게 작업을 전환하는 과정을
컨텍스트 스위칭(Context Switching)이라고 한다.
그런데 프로세스라는 단위 자체가 Context Switching에는 적합하지 않은 실행 단위이다.
각각의 프로세스는 고유한 스택과 데이터 영역을 가지고 이는 침범하지 않도록 보호받기 때문이다.
가령 프로그램 안에 동시 작업을 위해서 여러 프로세스가 실행되어야 한다고 가정하자.
그러면 필요한 만큼의 프로세스를 추가 생성하고, 정보를 복사해줘야하고, 프로세스 제거를 해야 한다.
그렇지만 프로세스 특성상 생성, 제거가 느리고, 정보 교환이 어려운 데다 그 크기 또한 상당하다.
따라서, 사실상 프로세스를 통한 동시 작업은 비효율적이며 오버헤드가 크다.
이에 따라 나오게 된 개념이 바로 프로세스보다 작은 스레드라는 개념이며,
스레드는 프로세스는 해결하지 못했던 자원의 공유 문제를 해결한다.
2. 스레드로 더욱 효과적인 Context Switching, Scheduler
그러면 스레드의 핵심 기능 중 하나는 바로 프로세스는 버거웠던 자원의 분배(공유)가 될 것이다.
기존의 스레드가 프로세스에서 작업을 하던 중 다른 스레드로 전환이 될 때, Context Switching을 한다면
애초부터 같은 프로세스 내에 스레드가 있었기에 컨텍스트 스위칭시에 발생하는 자원이 줄어든다.
또한 단일 스레드였더라도, 추가적으로 스레드를 생성 그리고 작업 후 소멸 시에
프로세스보다는 생성, 제거가 빠르기에 유연하고 섬세한 컨텍스트 스위칭이 가능해졌다.
한편, OS의 커널(Kernel)은 CPU가 어떤 프로세스를 작업할지 결정하는 스케줄러(Scheduler)라는 기술을 통해
여러 작업을 동시에 수행한다. 그리고 이 스케줄러는 스레드의 도입으로 인해서 병렬성과 응답성을 높였다.
(자원의 효율적 분배를 위한 알고리즘+기술이 집약되었다고 보면 된다.)
현대 OS는 기본적으로 하나의 프로세스 내에 여러 실행 흐름, 다시 말해 여러 스레드를 두어
병렬적, 혹은 병렬적으로 보이도록 작업을 처리할 수 있게 되었고, 이를 멀티스레딩이라고 한다.
커널의 스케줄링은 기존에는 프로세스 단위로 CPU의 작업을 스케줄링하도록 진행해 왔으나,
이제는 프로세스 내의 스레드 단위로 스케줄링을 진행하기에 효과적인 다중 작업의 진행이 가능하다.
다만 스레드는 기본적으로 프로세스 내의 공유 메모리를 사용하기에,
여러 실행 주체에 대한 단일 진입점 문제, 즉 동시성 문제가 발생할 수 있다.
그렇기 때문에 동시성 문제를 해결하기 위한 동기화 기법으로 사용되는 것이 Mutex와 Semaphore 기법이다.
다만 이에 대한 설명이 이 글의 주제는 아니므로 생략하겠다.
3. CPU의 8개의 스레드는 어떻게 Tomcat의 200개의 스레드를 작동시키는가?
사실 이 글을 쓰고자 했던 이유는 이 섹션을 쓰기 위함이었다.
현재 내가 가지고 있는 컴퓨터의 CPU는 4 코어 8 스레드의 스펙을 가지고 있다.
실질적으로는 4개의 물리 코어이지만, SMT(Simultaneous MultiThreading) 기술을 사용해
8개의 논리 스레드로 8개의 논리 코어를 가진 것처럼 보이게 하는 것이다.
그러면 나의 궁금증은 여기서 나온다.
Tomcat의 스레드 풀에는 기본적으로 200개의 스레드 풀을 유휴 스레드로 가지고 있다.
CPU는 어떻게 8개의 논리 스레드만으로 200개의 스레드 풀을 유지하면서
200개의 요청을 동시에 처리할 수 있는가?
이는
"Windows가 어떻게 수많은 서비스를 백그라운드에서 작동시키면서
추가적으로 여러 프로그램을 동작시키는가"
에 대한 대답과 같다. OS 커널에서 여러 프로세스에 대한 스레드를 스케줄링을 통해 여러 프로세스를 작업하면서
동시에 필요한 순간이 오면 다시 원래 프로세스의 스레드로 돌아와 해당 작업을 진행하는 방식처럼,
JVM에서도 요청이 들어왔을 때 Tomcat의 Serlvet Container는 Catalina를 통해 스레드 풀에서 스레드를 할당하고,
현재 작업이 진행될 경우에는 cpu에서 해당 작업을 처리한다.
그리고 작업이 끝나거나 다른 작업의 병렬 요청이 들어올 경우,
스케줄링과 컨텍스트 스위칭을 통해 다른 요청이 들어온 스레드의 작업을 진행하는 방식으로 작동되는 것이다.
4. 여러 가지 소개되는 스레드와 이들 간 정량적 비교
- CPU 스레드(8) : 실질적으로 작업을 처리하는 CPU의 논리 단위
- 커널 스레드(플랫폼 스레드) (3706 - 그중 Tomcat에 할당된 것은 200) : 프로세스를 이루고 있는 여러 개의 작은 실행 단위
- OS에서 스케줄링과 컨텍스트 스위치를 통해 어떤 스레드에서 작업을 할지 결정, 스레드 전환
- 유저 스레드 : JVM 내에서 다뤄지는 스레드
- 사용자(프로그램 코드 레벨)에서 직접 만들고 다룰 수 있는 스레드를 말한다.
용례 별로 아래의 두 가지가 모두 유저 스레드라고 불리는데, - 전통적인 자바 스레드(Tomcat에 할당된 200+@)
- Native Thread이다. JNI를 통해 JVM 내부 native 코드로 OS 스레드를 생성한다.
- 다시 말해서, 전통적인 자바 스레드는 네이티브(커널) 스레드이며
따라서 커널 스레드와 전통적인 자바의 유저 스레드는 1:1 매핑이 된다. - new Thread()를 통해 생성하고, start()로 실행하는 스레드
- 혹은 Tomcat에서 스레드풀을 통해 형성되는 스레드
- Virtual Thread(플랫폼 스레드와 M:N 매핑 - N이 더 많음.)
- Project Loom 이후 JDK 21에 정식으로 나오게 된 Java의 경량 스레드 모델
- JVM 내부에 플랫폼 스레드 위에서 여러 개의 가상 스레드를 다룬다.
- ForkJoinPool이 가상 스레드 간 task(단위 스케줄링)을 진행한다.
- 사용자(프로그램 코드 레벨)에서 직접 만들고 다룰 수 있는 스레드를 말한다.
5. ForkJoinPool과 Parallel Stream
ForkJoinPool이라는 것은 병렬스트림에서도 나오는 내용이기에 잠깐만 언급하자면,
ForkJoinPool은 플랫폼 스레드 풀을 제공하는 기반이다.
새롭게 생성이라기보다는, 기본적으로 Tomcat의 스레드풀처럼 미리 일정량의 스레드 풀을 정해둔다.
가상 스레드는 이 기반 위에서 유연하게 스케줄링되는 스레드로 동작하며,
그렇게 따로 가상 스레드를 설정하지 않는다면
병렬스트림에서 사용하는 스레드는 기본적으로는 OS 커널을 통한 플랫폼 스레드를 활용할 것이다.
추가적으로, SecurityContextHolder의 ThreadLocal의 정보를 활용해야 할 때,
Parallel Stream의 사용은 주의를 요한다.
왜냐하면 Parallel Stream은 ForkJoinPool으로부터 플랫폼 스레드를 할당받아와서
여러 작업을 병렬로 수행하기 때문에, 새롭게 할당받은 스레드는 SecurityContext의 ThreadLocal의 정보에
접근할 수 없다. 따라서 이 경우에는 SecurityContextHolder의 Mode를 MODE_INHERITABLETHREADLOCAL 으로 바꿔야 하위스레드가 접근이 가능해 병렬처리가 가능하게 될 것이다.
1. 프로세스 간 자원 공유가 불가능한 이유
**프로세스(Process)**는 운영체제(OS)에서 독립적으로 실행되는 프로그램의 단위입니다.
프로세스는 각자 고유한 메모리 공간을 할당받아 실행되며, 이로 인해 서로 다른 프로세스 간에는 자원을 직접적으로 공유할 수 없습니다.
운영체제의 메모리 보호
- 프로세스는 각각의 **주소 공간(Address Space)**을 할당받습니다.
- 이 주소 공간은 코드, 데이터(변수 등), 스택, 힙으로 나뉩니다.
- 운영체제는 메모리 보호(Memory Protection) 기능을 통해 각 프로세스가 할당된 메모리 외의 영역에 접근하는 것을 차단합니다.
이로 인해 발생하는 특징
- 프로세스는 자신의 메모리에만 접근할 수 있으며, 다른 프로세스의 메모리를 직접적으로 공유하거나 읽고 쓸 수 없습니다.
- 보안 및 안정성을 위해 운영체제가 이를 강제합니다.
프로세스 간 자원 공유 방법
프로세스 간에 자원을 공유하려면 운영체제가 제공하는 IPC(Inter-Process Communication, 프로세스 간 통신) 기법을 사용해야 합니다. 대표적인 방법으로는 다음이 있습니다.
- 파이프(pipe)
- 메시지 큐(Message Queue)
- 공유 메모리(Shared Memory)
- 소켓(Socket)
2. 프로세스 자원이 저장되는 위치
프로세스가 사용하는 자원(메모리)은 주로 **메인 메모리(RAM)**에 저장됩니다.
운영체제는 프로세스마다 다음과 같은 메모리 영역을 할당합니다.
코드(Code) | 실행되는 프로그램의 명령어가 저장되는 영역 |
데이터(Data) | 전역 변수 및 정적(static) 변수 등이 저장되는 영역 |
힙(Heap) | 동적 메모리 할당을 위해 사용되는 영역 |
스택(Stack) | 함수 호출 시 매개변수, 지역 변수 등이 저장되는 영역 |
운영체제는 각 프로세스의 주소 공간을 독립적으로 관리하기 때문에 다른 프로세스가 이 메모리 영역에 접근할 수 없습니다.
3. 스레드는 왜 자원을 공유할 수 있는가?
**스레드(Thread)**는 프로세스 내에서 실행 흐름의 단위를 의미합니다.
프로세스는 하나 이상의 스레드를 가질 수 있으며, 프로세스 내의 모든 스레드는 동일한 메모리 주소 공간을 공유합니다.
스레드의 자원 공유 특징
- 스레드는 코드, 데이터, 힙 영역을 프로세스 내 다른 스레드와 공유합니다.
- 다만, **스택(Stack)**은 스레드마다 독립적으로 할당됩니다. 각 스레드가 호출한 함수의 매개변수나 지역 변수는 해당 스레드의 스택에 저장됩니다.
왜 스레드는 자원을 공유하는가?
- 스레드는 프로세스 내부의 실행 단위이기 때문에 동일한 프로세스의 메모리 공간을 사용합니다.
- 이로 인해 같은 프로세스 내의 스레드들은 전역 변수, 동적 메모리(힙), 파일 디스크립터 등을 공유할 수 있습니다.
4. 프로세스와 스레드의 비교
항목프로세스스레드메모리 | 독립적인 메모리 공간을 가짐 | 같은 프로세스 내에서 메모리 공간을 공유 |
통신 | IPC와 같은 특별한 메커니즘 필요 | 같은 프로세스 내에서는 간단히 공유 가능 |
자원 | 독립적 자원 관리 | 프로세스 자원을 공유 |
안정성 | 다른 프로세스가 영향을 주기 어려움 | 한 스레드의 오류가 프로세스 전체에 영향을 줄 수 있음 |
속도 | 프로세스 간 전환 비용이 큼 | 스레드 간 전환 비용이 적음 |
5. 요약
- 프로세스는 독립적인 메모리 공간을 할당받기 때문에 다른 프로세스와 자원을 직접적으로 공유할 수 없습니다.
- 스레드는 동일한 프로세스의 메모리 공간(코드, 데이터, 힙)을 공유하기 때문에 자원 공유가 가능합니다.
- 프로세스 간 자원 공유를 위해서는 IPC 메커니즘을 사용해야 하며, 스레드는 이러한 메커니즘 없이도 쉽게 자원을 공유할 수 있습니다.
https://aaronryu.github.io/2021/03/14/thread-and-security-context-holder-mode/
Spring Security: SecurityContextHolder 의 Thread 공유 전략
다수 정보를 리스트로 조회하는 페이지에서 현재 로그인한 유저가 가진 권한에 따라 일부 정보를 보여주지 않도록하는 처리가 필요했습니다. 그래서 먼저 리스트를 API 로부터 가져온 뒤, 현재 S
aaronryu.github.io
https://wikidocs.net/book/2184
IT 기술 노트
책이라기 보다는 기술 노트 정도로 생각하시고 참고 하시면 좋겠습니다. 컴퓨터 구조부터 플랫폼까지 IT 전반의 다양한 내용들을 정리해봤습니다. 기술사를 공부하면서 공부했…
wikidocs.net
https://techblog.woowahan.com/15398/
Java의 미래, Virtual Thread | 우아한형제들 기술블로그
JDK21에 공식 feature로 추가된 Virtual Thread에 대해 알아보고, Thread, Reactive Programming, Kotlin coroutines와 비교해봅니다.
techblog.woowahan.com
https://ko.wikipedia.org/wiki/%EC%8A%A4%EB%A0%88%EB%93%9C_(%EC%BB%B4%ED%93%A8%ED%8C%85)
스레드 (컴퓨팅) - 위키백과, 우리 모두의 백과사전
위키백과, 우리 모두의 백과사전. 두 개의 스레드를 실행하고 있는 하나의 프로세스. 스레드(thread)는 어떠한 프로그램 내에서, 특히 프로세스 내에서 실행되는 흐름의 단위를 말한다. 일반적으
ko.wikipedia.org
https://en.wikipedia.org/wiki/Thread_(computing)
Thread (computing) - Wikipedia
From Wikipedia, the free encyclopedia Smallest sequence of programmed instructions that can be managed independently by a scheduler A process with two threads of execution, running on one processor Program vs. Process vs. Thread Scheduling, Preemption, Con
en.wikipedia.org
https://bugs.mysql.com/bug.php?id=109346
MySQL Bugs: #109346: Add support for Virtual Threads on the JDK
bugs.mysql.com