1. 서론
스프링부트의 WAS는 각 요청을 스레드를 할당해서 처리한다. 이때 일반적으로 스레드 풀 방식을 사용한다. 그리고 각각의 스레드는 스택에 할당된다. 그렇기 때문에 스레드에서 함수호출이 가능하다. 한편, JPA의 영속성 컨텍스트는 스레드 단위로 할당된다. 그렇기에 Lost update 문제가 발생할 수 있으며, 이는 비관적, 낙관적 락 등으로 해결할 수 있다. 이처럼 스레드는 백엔드 개발에 아주 기본적인 개념이라고 할 수 있다. 이번 글에서는 시스템 프로그래밍 시간에 배운 스레드 내용 중 중요하다고 생각한 부분을 정리해보려고 한다.
2. 스레드 (Threads)
2.1 스레드 기본 개념 및 특징
- 스레드는 프로세스에 포함된 실행단위이다.
- 이 실행 단위는 독립적으로 CPU 자원을 할당받고 실행되는 별도의 컨텍스트(PC, 스택)를 의미한다.
- 스레드는 프로세스의 컨텍스트에서 실행된다. 즉 하나의 프로세스에 속한 스레드는 같은 Address space를 공유한다. 이 안에서 실행단위는 여러개가 존재하며 각각을 스레드라 한다.
- 프로세스 간 데이터를 공유하기 위해서는 메시지 큐 혹은 파이프를 사용해야 하지만, 같은 프로세스에 속한 스레드간의 데이터 공유는 동일한 Address space를 공유하므로 상대적으로 쉽다.
- 일반적으로 멀티프로세스보다 멀티스레드 방식이 효과적이다.
- 스레드는 기존 프로세스의 메모리 공간(코드, 전역 데이터, 힙)을 공유하면서 독립적인 스택과 레지스터만 새로 할당되기 때문에, 프로세스 생성에 비해 오버헤드가 훨씬 적기 때문이다.
2.2 추가적인 특징
- 프로세스가 생성되면 main 스레드가 생성된다.
- Context Switch는 프로세스 스레드 모두에게 적용 가능하다.
- 동일한 프로세스에 속한 스레드간 Context Switch가 발생하면 Address space는 변하지 않는다.
즉 스레드간 Context Switch는 스택과 레지스터만 교체하면 되므로 프로세스에 비해 상대적으로 가볍다.
2.3 스레드 할당 구조
위 사진을 통해 서로 다른 프로세스는 독립적인 Address space를 가지며, 하나의 프로세스에 속한 스레드들은 별도의 스택에 할당된다. 즉 별도의 컨텍스트를 갖음을 알 수 있다. 이는 한 스레드의 실행이 다른 스레드에 영향을 주지 않음을 의미한다. 또한 프로세스 내부의 힙, 전역변수, 코드 등의 영역을 공유하는 것을 알 수 있다.
3. 스레드 설정
c 언어에서 POSIX 스레드를 생성하기위한 시스템콜은 아래와 같다.
이 시스템콜을 사용할때 프로그래머가 직접 설정할 수 있는 내용을 바탕으로 스레드에 대한 이해를 더욱 높여보자.
직접 설정할 수 있는 내용은 attr 변수에 정의 되어있으며 중요하다고 생각하는 것들을 아래에 정리하도록 하겠다.
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start)(void *),
void *arg);
3.1 Detach State
스레드가 종료되었을때 스레드의 자원을 정리하는 방식을 의미하며 두가지 방식이 있다.
Joinable 상태
- 스레드는 기본적으로 Joinable 상태로 생성된다.
- 스레드가 종료되더라도 자원이 즉시 정리되지 않고 남아있습니다.
- 스레드 종료 후에도 자원을 수거하지 않으면 메모리 누수가 발생할 수있다.
- pthread_join() 을 호출해야 해당 스레드의 관련 자원이 해제된다.
- 자원을 해제하지 않으면 좀비 프로세스처럼 시스템의 메모리를 차지하게 된다.
Detached 상태
- 스레드가 Detached 상태로 생성되면, 스레드가 종료되는 즉시 운영체제가 해당 스레드의 자원을 자동으로 정리한다.
3.2 Scope
기본적으로 운영체제는 프로세스, 스레드를 구분하지 않고 CPU를 할당한다. 이 설정을 이용하여 생성 스레드의 스케줄링 방식을 결정할 수 있다. 즉 스케줄링의 Scope를 설정하여 생성 스레드의 경쟁 대상을 결정할 수 있다는 의미이다. 하나의 프로세스에 속한 스레드들과 경쟁 하거나 시스템 전체 범위에서 경쟁할 수 있도록 설정할 수 있다.
3.3 Guard Size
앞에서 스레드는 스택에 할당된다고 했다. 그런데, 두 스레드가 할당된 스택의 영역이 너무 가까우면 하나의 스레드가 실행되면서 자라난 스택이 다른 스레드의 스택으로 침범할 수 있다. Guard Size는 이러한 잠재적 문제를 방지하기 위한 방법으로서, 스택의 끝에 보호 메모리가 할당된다. 스레드가 할당된 범위를 초과해 이 보호 메모리에 접근하면 Segmentation Fault와 같은 오류를 발생시킨다. 기본 값은 시스템의 메모리 페이지 크기인 4KB이다.
3.4 Stack Size
Stack Size는 스레드가 실행될 때 사용할 수 있는 스택 메모리의 크기를 의미한다. 스택은 함수 호출 시 지역 변수를 저장하거나 함수 호출 기록(리턴 주소 등)을 관리하는 공간으로 사용된다. 각 스레드는 독립적인 스택 영역을 가지며, 이를 통해 함수 실행 흐름과 관련된 데이터를 저장한다. 기본값은 8MB이다.
4. 마치며
스레드를 사용하면서 발생하는는 동기와 문제, 데드락 같은 문제는 다른 포스트에서 다루도록 하겠다. 스레드는 백엔드에서 가장 기본적인 개념이니 꼼꼼히 학습하도록 하자!
'CS' 카테고리의 다른 글
OS 파일 시스템: 파일 테이블과 I/O 성능 개선 기법 (0) | 2024.12.30 |
---|---|
OS가 관리하는 Clock & Hardware Clock (0) | 2024.12.15 |
표준 I/O 라이브러리의 등장 배경: 시스템 콜 성능 최적화 (1) | 2024.12.08 |
OS의 I/O Device 추상화 & Block I/O & Non-Block I/O: Multiplexing (0) | 2024.12.01 |
CPU & I/O Bound Job: 최적의 스레드 개수 (0) | 2024.11.21 |