본문 바로가기

Study

스레드(Thread)

스레드에 대해 공부해 보자.

스레드를 알기 위해선 프로세스(Process) 의 개념을 먼저 알아야 한다. 프로세스는 간단하게 실행중인 프로그램을 일컫는데, 실행중이라는 것은 프로그램이 운영체제에 의해 메모리를 할당받아 돌아가고 있는 상태를 말한다.

스레드는 프로세스 내에서 실제 작업이 실행되는 흐름, 또는 작업을 수행하는 주체를 의미한다. 모든 프로세스에는 한 개 이상의 스레드가 존재하며, 하나의 프로세스에 여러 개의 스레드가 존재할 수 있는데 이러한 실행 방식을 멀티스레드(multi-thread) 라고 한다. 
스레드 전체를 속속들이 공부하는것은 꽤나 양이 방대하고, 당장 스레드를 정리하면서 그만큼의 깊이를 가져가기는 어려울 것이라 생각되기 때문에 이번에는 스레드에 대해서 어느정도 핵심이라고 생각되는 것, 궁금했던 것에 대한 키워드들만 정리해 볼 생각이다. 

 

- 프로세스와 스레드(멀티스레드) 개념과 차이 명확히 파악하기

스레드를 알아보려면 먼저 프로세스에 대해 알아야 한다. 프로세스는 실행 중인 프로그램 이라는 뜻으로, 스레드보다 더 큰 흐름의 실행 단위를 뜻한다. 한 프로그램이 여러 프로세스를 실행시키는 것을 멀티 프로세스라고 하며, 이는 멀티스레드와 비슷하게 느껴질 수 있지만 전혀 다른 단위이다. 프로세스와 스레드의 차이를 나누는 기준은 여러 가지가 있겠지만, 가장 큰 차이는 공유 메모리이다. 프로세스는 각각의 스택, 데이터, 힙 메모리를 할당받아 독립적으로 사용하고, 이로 인해 프로그램 안전성을 얻지만 자원 비효율성이나 프로세스 간 통신 오버헤드, 컨텍스트 스위칭 오버헤드 등 여러 단점을 가진다.
스레드는 프로세스와 달리, 프로세스 내에서 공유하는 메모리를 가진다. 즉 자원 효율성을 높일 수 있으며, 공유 메모리로 인해 스레드 간 통신도 프로세스보다 훨씬 가볍다. 

 

- 프로세스 내에서 스레드가 공유하는 것

멀티 스레드에서, 스레드는 프로세스 내의 메모리 공간을 공유한다. 정확히는 힙 메모리, 데이터 세그먼트, 코드 세그먼트와 IO 리소스 등을 공유하고, 정적, 전역 변수에 접근할 수 있다. 이로 인해 스레드 간 통신이 프로세스에 비해 훨씬 간단하지만, 반대로 특정 자원에 동시 접근하는 스레드에 대한 제어가 필요하기 때문에 이로 인한 성능 저하가 발생할 수 있다.공유 자원에 대한 접근 문제를 해결하기 위해 스레드는 뮤텍스(Mutex) 와 세마포어(Semaphore) 방식을 활용한다. 이는 트랜잭션에서의 데이터 접근에서와 마찬가지로 락을 획득하고 해제하는 방식으로 접근을 제어한다. 
또 서로 필요한 자원을 점유하면서 대기하는 데드락이 발생할 수 있는데, 이를 방지하기 위해 점유와 대기, 상호 배제, 비선점, 순환 대기 등의 알고리즘을 사용한다. 이런 문제들로부터 데이터 손상 방지와 안전성을 보장하기 위해서는 신중한 동기화 처리가 필요하다.

 

- 커널 레벨 스레드, 사용자 레벨 스레드(자바에서 생성 가능한 스레드)

그렇다면 커널 레벨 스레드와 유저 레벨 스레드는 뭔가 하는 궁금증이 생겼다. 개념적으로는 커널 레벨 스레드는 운영체제 레벨에서 지원하며, 운영체제의 스케줄러가 스레드의 동작을 관리한다. 유저 레벨 스레드는 운영체제와 관계 없는 프로그래밍 레벨에서 생성 및 관리되는 스레드이며, 커널(운영체제)에서는 유저 레벨 스레드의 존재에 대해서도 알지 못한다. 예를 들어 자바 프로그램에서 Runnable 인터페이스를 상속받아 스레드를 만들었다면, 이것이 유저 레벨(프로그래밍 레벨) 스레드이다.
그러나 유저 레벨 스레드는 결국 커널 레벨 스레드로서 실행되기 때문에(?), 유저 레벨 스레드가 실행되기 위해서는 반드시 커널 레벨 스레드와 연결되어야 한다. CPU에서 실행되는 것은 결국 커널 레벨 스레드이고, 유저 레벨 스레드는 커널에게 스케줄링과 스레드 관리를 위임함으로써  CPU 에서 실행이 되는 것이다.

사용자 레벨 스레드는 커널 스레드에 비해 성능이 조금 좋은 대신 멀티코어의 이점을 활용할 수 없고, system call 등으로 인해 하나의 스레드가bloking 됐을 때 프로세스 내의 다른 스레드도 같이 blocking 되는 단점이 있다. Java 의 스레드는 사용자 레벨 스레드로, 출시 초기에는 JVM 이 스레드의 스케줄링과 생명주기를 관리하는 green thread 모델을 사용했었다.(이 경우에도 사용자 스레드 → 커널 스레드로 다대 일 매핑을 하여 사용한다) 그러나 Jdk 1.3 이후 버전부터는 스레드의 스케줄링과 관리를 OS 에 위임하는 방식인 natural thread 모델을 사용한다. 엄밀히 말하면 사용자 스레드이지만, 커널 스레드와 One to One threading model 을 통해 1:1 로 매핑하여 커널 수준에서 스케줄링되는 것과 같은 특징을 나타낸다. 이 때문인지 자료조사 중 natural thread 를 OS thread(커널 스레드) 라고 기록한 레퍼런스도 있었다.

 

- 스레드와 트랜잭션의 관계

스레드의 성질을 들여다보면, 트랜잭션과 닮은 부분이 있다는 것을 알 수 있다. 공유 데이터를 접근하는 스레드에 대한 제어를 하는 것도 비슷하고, 발생하는 문제도 비슷하다. 또, 트랜잭션이 발생하면 서버는 하나의 스레드에 하나의 트랜잭션을 할당할까? 하는 의문도 생긴다. 궁금했던 점은 이 부분이었다.
결론부터 말하자면, 스레드 하나당 여러 개의 DB 커넥션을 사용할 수 있고, 스프링에서는 같은 원리로 하나의 스레드당 여러 트랜잭션을 관리한다. 스레드 하나에 여러 커넥션을 사용하는 이유는 1. 요청이 늘어날 때 컨텍스트 스위칭 비용을 줄이기 위해, 2. 스택 메모리를 절약하기 위해서라고 한다. 주된 이유는 스택 메모리 절약인데, 커넥션이 무한정 늘어나는 상황에서 스레드가 커넥션 개수를 따라간다면 공유되지 않는 메모리 영역인 스택 메모리가 많이 할당되는 문제가 발생하는 것이 이유인 것으로 보인다.

ref : https://zzang9ha.tistory.com/414

 

 

스레드에 대해 개념적인 부분을 간단히 알아봤지만, 보다 깊이 운영체제 측면에서 스레드가 어떻게 관리되고 생성되는지, 그 흐름을 공부해야 할 것 같다. 근본적인 개념에 대해 깊이 공부하다 보면 지금 희미하게 와닿는 부분들도 더 확실히 알게 될 것이라고 생각한다.
더해서, 현재 사용하는 언어와 프레임워크인 Java, Spring 에서 어떻게 멀티스레딩을 구현하는지는 다음 글에서 정리하였다.

https://shnowball.tistory.com/entry/%EC%8A%A4%EB%A0%88%EB%93%9C2-SpringBoot-Tomcat-%EC%97%90%EC%84%9C%EC%9D%98-%EC%8A%A4%EB%A0%88%EB%93%9C

 

스레드(2) - SpringBoot (Tomcat) 에서의 멀티스레딩

우리가 개발하는 웹 애플리케이션(SpringBoot)에서 요청이 어떻게 멀티스레딩 되는지 알아보려면, 우선 스레드 풀에 대해 다시 짚어볼 필요가 있다. 스레드 풀은 응답시간과 TPS(Transaction Per Sec) 를

shnowball.tistory.com