쓰레드(Thread)란 프로세스 내에서 독립적으로 실행되는 단위로써, 실제로 작업을 수행하는 주체를 의미하고, 하나의 프로세스는 여러 개의 쓰레드를 가질 수 있다.
웹 서버나, 데이터 베이스 등 실시간 애플리케이션에서는 쓰레드를 활용하여 높은 성능과 응답성을 제공한다. Spring Boot
에서 기본적으로 사용하는 Tomcat
서버도 기본적으로 여러 개의 쓰레드를 활용하여 클라이언트의 요청을 처리한다.
좌측의 이미지는 일반적인 프로세스 메모리 공간의 모습이고, 우측의 이미지는 다수의 쓰레드가 생성된 프로세스 메모리 공간의 모습이다.
Thread 는 독립된 실행단위이기 때문에 각각의 Stack
과 기타 레지스터
들을 각각의 Thread 마다 가지고 있다.
반면 Heap
, Data(Static)
, Code
와 같은 영역은 그 어느 Thread 에서도 접근이 가능하도록 공유 되어있다.
이처럼 Thread는 프로세스 내의 Heap
, Data(Static)
, Code
영역을 모두 공유하고 있어서, 언제 어디서든 각각의 영역에 접근이 가능하다.
그렇기 때문에 각각의 Thread에서 프로세스가 가지고 있는 Code 데이터를 이용해서 함수를 자유롭게 호출할 수 있다. Heap, Data 영역도 역시 공유되기 때문에 IPC 없이도
쓰레드 간의 통신이 가능하다. 이는 다수의 쓰레드가 통신으로 주고받고자 하는 데이터를 Heap 영역에 할당하여 각각의 쓰레드가 자유롭게 접근하여 사용한다고 생각하면 된다.
쓰레드 제어 블록(Thread Control Block 이하 TCB)
이란 운영 체제에서 쓰레드를 관리하기 위해 사용하는 데이터 구조이다. TCB는 각 쓰레드의 상태와 제어 정보를 저장하여 쓰레드의 실행과 관리에 필요한 모든 정보를 포함한다.
PCB(Process Control Block)
에 비해 적은 데이터를 저장하며, 이 때문에 Context Switching
시 Process
보다 더 빠르고 가볍다는 장점이 있다.
TCB에는 다음과 같은 정보가 포함 되어 있다.
쓰레드 ID
쓰레드 상태
CPU 정보
우선 순위
PCB를 가리키는 포인터
스레드 생성 스레드를 가리키는 포인터
Stack 포인터
Context Switching
이 발생하면, 현재 실행 중인 위와 같은 스레드의 정보를 TCP에 저장하고, 다른 스레드의 TCB에 저장된 정보를 불러와 복원하여 실행한다.
Context Switching 시, TCB가 PCB보다 빠른 이유
PCB는 프로세스 전체의 상태를 관리하는 데이터가 포함되어 있으므로, 상대적으로 TCB에 저장되는 데이터보다 양이 많다. 스레드는 프로세스 안에서 작은 실행 단위이기 때문에 그렇다. 또한 스레드는 다른 스레드와 자원을 일부 공유하기 때문에 TCB에 관리할 데이터가 더 적다. 하지만Process Context Swiching
시에는 사용하는 메모리 공간 자체가 아예 다르기 때문에 메모리 공간을 전환해야 하는 등Thread Context Switching
보다 오버헤드가 더 발생한다고 이야기 할 수 있다.
먼저 쓰레드는 2가지의 쓰레드로 크게 나뉜다.
User Level Thread
먼저 User Level Thread
는 커널에서 직접 생성하고 제어하는 것이 아니라, User Level
에서 생성되고 관리된다. User Level Thread
는 커널에서 해당 Thread
를 인식하지 못하기 때문에 사용자가 Thread
관련
라이브러리를 구현하여 스케줄링하고 생성에 관여한다. 하지만 User Level Thread
는 I/O 작업이 생길 경우, 다른 Thread
모두 대기 상태가 된다. 왜냐하면 커널은 User Level Thread
에 대해서 인식하지 못하기 때문에
해당 I/O 작업이 일어난 Process
를 대기(Blocked)
상태로 변경한다. 결국 해당 프로세스 내에 존재하는 모든 Thread
가 대기 상태에 이르게 된다.
User Level Thread
는 커널에 독립적으로 Thread
를 스케줄링 할 수 있기 때문에 모든 운영체제에 적용할 수 있는 이식성이 높고, 스케줄링이나 동기화를 위해 커널 영역의 기능을 쓰지 않아, 커널 전환 시 발생하는 오버헤드가 줄어듭니다.
다만 커널이 User Level Thread
들의 알지 못하면서 생기는 부수효과들이 문제가 될 수 있습니다. (e.g. I/O 작업 시 모든 Thread Blocked)
Kernal Level Thread
Kernal Level Thread
는 커널이 Thread 생성과 스케줄링 등 관련된 모든 작업을 관리하는 방식이다. User Level Thread
와 Kernal Level Thread
가 One to One 매핑이 되고, 커널이 직접 스케줄링하고 관리하기 때문에 관리에 대해 많은 지원을 받을 수 있다.
하지만 오버헤드도 그만큼 늘어나게 된다. 또한 커널이 각 Thread
를 개별적으로 관리하므로, 어떠한 Thread
에서 I/O 작업이 일어나도, 다른 쓰레드들이 대기 상태에 이르지 않고 작업을 계속 처리할 수 있다.
JVM의 Thread 구조는 어떻게 되어있을까?
https://letsmakemyselfprogrammer.tistory.com/98
멀티 쓰레드 프로그래밍(Multi-Thread Programming)
은 하나의 프로그램이 여러 쓰레드를 사용해 동시에 여러 작업을 수행하는 프로그래밍 방식이다. 여러 작업을 동시에 처리할 수 있기 때문에 프로그램의 성능이 향상되지만 여러 쓰레드가 같은 메모리 자원에 접근하면서 생길 수 있는
동기화 문제로 인해 데이터가 깨질 수 있다. 이를 해결하기 위해 Lock 과 같은 동기화 메커니즘을 사용할 수 있다. 하지만 이 때문에 코드가 복잡해지고 데드락(DeadLock)
문제가 발생할 수 있다.
장점
단점
Lock(락)
과 같은 동기화 메커니즘을 사용하여 데이터를 일관성 있게 유지해야 한다.다음과 같은 코드를 통해 Java에서 멀티 쓰레드를 사용할 수 있다.
class MyThread extends Thread {
public void run() {
for (int i=0; i<5; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
thread1.start();
thread2.start();
}
}
멀티 프로세스가 있는데, 멀티 쓰레드를 사용하는 이유
멀티 프로세스
는 각 프로세스가 독립적인 메모리 공간을 갖는데에 비해,멀티 쓰레드
방식은 각 쓰레드가Stack
과Register
를 제외한 공간은 모두 공유한다. 그렇기 때문에 메모리 공간을 더 효율적으로 사용함으로써메모리 효율성
을 높일 수 있다.멀티 프로세스
는Context Switching
비용이멀티 쓰레드
방식에 비해 높기 때문에멀티 쓰레드
방식을 사용하게 되면Context Switching 시 발생하는 오버헤드
가 적고,Context Switching
속도가 빠르다.- 각 쓰레드 간에 데이터를 교환할 때에는 프로세스 처럼
IPC
가 아니라 공유된 메모리를 통해 데이터를 공유할 수 있기 때문에 데이터복사 및 통신 비용
또한 줄어들게 되고, 이로인한코드 복잡성
역시 줄어들게 된다.- 멀티코어 환경에서
멀티 프로세스
,멀티 쓰레드
모두 병렬 작업 처리가 가능하지만, 앞서 이야기한 멀티 프로세스 방식에서는 오버헤드가 멀티 쓰레드 방식보다 더 발생하기 때문에 자원을 더 효율적으로 활용하는 데에 있어서는 멀티 쓰레딩 프로그래밍이 더 적합하다.
멀티 쓰레딩 프로그래밍 시 주의할 점
- 멀티 쓰레딩 프로그래밍 문제의 핵심은 바로 하나의 자원에 대해 여러 쓰레드가 접근이 가능하다는 점이다. 만약 불변해야 하는 데이터가 여러 쓰레드 중 하나의 쓰레드에 의해 변경이 되었다면 그 값을 읽어서 사용하는 다른 쓰레드들은 모두 영향을 받거나, 최악의 경우 작업을 정상적으로 처리할 수 없게 된다. 이러한 동기화 문제를 해결하기 위해서 Lock(락)과 같은 동기화 메커니즘을 도입해서 해결할 수 있는데, 이 역시도 DeadLock과 같은 문제점을 야기할 수 있기 때문에 멀티 쓰레딩 프로그래밍 시 교착 상태에 빠지지 않게 하거나, 동기화 문제를 주의하며 애플리케이션을 개발해야 한다.
Thread-Safe 란?
Thread-Safe란 위에서 제시한멀티 쓰레딩 프로그래밍 시 주의할 점
의 문제를 해소한 상태이다. 즉, 멀티 쓰레드 프로그래밍에서 공유된 자원에 대해 동시에 접근이 이루어졌을 때, 프로그램 실행에는 아무 문제가 없는 상태를 말한다. 이 상태에서는 두 개 이상의 쓰레드가 Race Condition에 빠지지 않고, 결과에 대한 정합성이 보장 되었음을 나타낸다.
이에 대해 동기화에 대한 범위를 최소화하거나, 자원을 얻는 순서나 타임 아웃에 대해 설정하고, 가능하면 불변 객체를 사용하여 접근하는 자원에 대해 그 어떤 쓰레드에서도 자원을 변경할 수 없도록 설정한다면 Thread-Safe 하고 안전한 멀티 쓰레드 프로그래밍을 할 수 있다.