프로세스와 스레드

프로세스와 스레드의 개념 이해 및 스레드의 필요성, 스레드 모델 등을 이해하기

프로세스

프로세스란?

프로그램을 실행 하여 메모리에 올라온 상태로 운영체제 안에서 실행중인 프로그램을 프로세스라고 한다.

프로세스는 프로세스 고유의 메모리 구성을 통해 독립적으로 동작한다.

  • 프로세스는 하나 이상의 스레드를 반드시 포함한다.

프로세스의 메모리 구성

  • 코드: 실행할 프로그램의 코드가 저장되는 영역

  • 데이터: 전역 변수, 정적 변수가 저장되는 영역

  • 힙: 동적으로 할당되는 메모리 영역으로 윈도우에서 DLL 같은 파일이 속함

  • 스택: 함수 호출 시 생성되는 프레임과 지역 변수가 저장되는 영역

멀티 태스킹

프로세스는 개별 실행 프로그램이 현재 실행되어 메모리에 올라온 상태라고 했을 때, 여러 개의 프로그램을 동시에 실행하게 된다면 순차적으로 종료될 때 까지 기다렸다가 실행하지 않는다.

운영체제는 스케줄링에 의해 실행 시켜야 할 프로세스를 굉장히 빠른 속도로 작업 단위를 번갈아가며 동시에 여러 작업을 수행하는 것 처럼 실행 시킨다.

이로써 사용자는 여러 프로그램을 동시에 이용하는 것 처럼 느낄 수 있게 되었지만, 프로세스가 가지고 있는 고유의 데이터 영역을 모두 기억하여 불러와야하는 컨텍스트 스위칭 과정이 많이 발생하게 된다.

이 때, 프로세스가 가지고 있는 정보를 PCB(Process Control Block) 이라고 하며 프로세스를 가르키는 고유의 정보들을 담고 있다가 서로 컨텍스트 스위칭이 진행될 때 이 PCB 를 참조하게 된다.

  • 이 과정이 굉장히 많이 발생하면 컴퓨터의 성능은 당연히 버티지 못할 것이고 사용자도 "응답없음" 화면을 자주 마주치게 된다.

멀티 프로세싱

CPU 코어가 제한적일 때 최대 효율을 내기 위해 멀티 태스킹 전략을 사용했지만, 사용자는 불편함을 느끼고 기술은 나날이 발전하여 물리적 코어 갯수를 늘리게 된다.

결론적으로 프로세스 여러개를 한 개의 코어에서 짧게 여러 프로세스를 아주 작은 단위로 나누어 실행 하며 컨텍스트 스위칭에 단점을 개선하고자, 물리적으로 여러 개의 코어를 두어 개별 코어가 병렬적으로 프로세스를 하나씩 실행 시키도록 구성한다.

물리적 코어가 개별 프로세스를 실행 하면, 프로세스 간 자원을 공유 해야하는 상황이 발생할 수 있다.

예를 들어 인터넷 브라우저에 탭을 여러개 띄웠는데 개별 탭이 하나의 프로세스로 동작한다고 했을 때 서로 띄워둔 탭 간의 정보를 공유하고 싶을 수 있을 것이다.

프로세스는 메모리 구성이 독립적이기 때문에 서로의 메모리 영역을 접근할 수 없어 자원을 주고 받기 위해 IPC 라는 통신 매커니즘으로 데이터를 주고 받는다.

  • 이 과정에서 IPC 를 위한 관리 포인트가 생기기도 하고, 프로세스를 여러 개 실행 했을 때 CPU 사용률도 높아지기 마련이다. 이러한 단점을 해결하기 위해 단일 스레드가 아닌 멀티 스레드로 동작 시키는 방식이 사용된다.

스레드

스레드란?

프로세스가 독립적인 자원을 할당 받아 코드 덩어리를 한 줄씩 실행하는 작업의 단위를 스레드라고 부른다.

스레드는 프로세스의 메모리 구성 중 코드, 데이터, 힙 영역을 공유하여 사용한다.

데몬 스레드

데몬 스레드란?

백그라운드로 동작하는 스레드이며, 데모나이즈 과정에서 포크한 메인 스레드가 종료되면 같이 종료되는 것이 특징이다.

스레드의 메모리 구성

  • 공유 메모리: 같은 프로세스의 코드 영역, 데이터 영역, 힙 영역은 프로세스 내 모든 스레드가 같이 공유한다.

  • 스택 메모리: 개별 스레드는 자신만의 고유한 스택 영역을 할당 받는다.

스레드 컨트롤 블럭

스레드는 개별적으로 인지할 수 있도록 자신만의 고유한 속성 값들이 존재한다. 이 정보들을 바탕으로 어떤 스레드가 어디까지 작업을 실행 했는지, 현재 어떤 상태인지 등을 커널 수준에서 제어할 수 있게 된다.

스레드 속성

  • ID 와 이름

  • 우선순위: 높음, 보통, 낮음

  • 상태: 생성, 실행, 대기, 종료

  • 관계: 그룹

스레드는 CPU 자원을 사용하기 위해 대기열에 배치 되어 할당 받기를 기다리고 있는데 이 때, 어떤 스레드가 먼저 CPU 를 먼저 사용할지 배정받는지를 결정하는 것이 스레드 속성에서 "우선순위" 를 사용한다.

  • 평소에는 우선순위를 "보통" 으로 관리하게 된다. 만약, 우선순위가 불필요하게 "높음" 으로 많이 설정되면 CPU 자원을 너무 소모하기 때문에 성능 저하가 발생하여 보통으로 관리한다.

스레드는 어떻게 동작할까?

OS 스케줄러에 의해 아주 짧은 시간동안 CPU 코어를 점유하는 방식으로, 스레드가 여러개일 경우 이를 번갈아가며 사용한다.

스케줄러는 대기줄에 기다리고 있는 스레드가 대기 상태이거나, 블락 상태인 경우 우선순위 규칙에 따라 다음 수행할 스레드를 실행한다.

  • 이 때, 선점형 스케줄링 방식으로 다른 스레드가 작업이 다 끝나지 않았더라도 할당된 자원을 다 썼다면 준비 큐로 돌려보내고, 준비 큐에 있는 스레드를 실행 시키는 방식이다.

유저 스레드와 커널 스레드

유저 스레드

유저 스레드는 OS의 유저 모드에서 생성 및 관리 되는 스레드로, 주로 라이브러리를 통해 생성되는 스레드를 일컫는다.

  • 자바에서 과거 GreenThread 나 현재의 VirtualThread 가 이에 속한다.

유저 스레드의 특징

  • 유저 모드와 커널 모드간 이동이 없기 때문에 커널 스레드에 비해 상대적 컨텍스트 스위칭 비용이 덜하다.

  • TCB는 프로세스 내에서, PCB 는 커널에서 관리한다.

  • 유저 스레드는 라이브러리에 의해 생성 되기 때문에 N 개 이상이 존재할 수 있으며, 커널 스레드는 이를 1개의 프로세스로 인지하는 N:1 구조로 매핑된다.

  • 유저 스레드 동작 과정에서 스레드 하나라도 I/O 작업을 위해 시스템 콜을 하면, 커널은 유일한 커널 스레드를 BLOCKED 상태로 만들고 그 커널 스레드 위에서 돌던 다른 유저 스레드 모두 강제로 멈추게 된다.

커널 스레드

커널 스레드는 OS 가 지원하는 시스템 레벨의 기능을 지원 받으며 커널이 스레드의 생성 및 스케줄 등을 관리하는 스레드이다.

  • 모든 프로세스는 1 개 이상의 커널 스레드를 할당 받는다.

커널 스레드의 특징

  • 유저 스레드와 달리 시스템 콜을 하더라도 다른 스레드에 영향을 주지 않는다.

  • 커널이 전체 TCB와 PCB를 관리한다.

  • 프로세스 내에 스레드 라이브러리가 없어서 커널 스레드를 스케줄 하여 1:1로 매핑된 사용자 스레드를 동작시킨다.

  • 커널 스레드는 스케줄링 대상이 되어, 사용자 스레드를 호출하기 때문에 유저 모드와 커널 모드 간 이동이 발생하여 컨텍스트 스위칭 시간이 소요된다.

멀티 스레드

프로세스는 무조건 한 개 이상의 스레드를 보유하고 있다고 하였다. 그럼, 프로세스의 스레드가 여러 개로 구성되어 실행 된다면 어떨까?

  1. 프로세스가 운영체제로부터 할당 받은 메모리 영역을 공유할 수 있게된다. 여기서 공유하는 메모리 영역은 코드, 데이터, 힙 영역이다.

  2. 개별 스레드가 보유하고 있는 스택만 새로 할당되기 때문에 멀티 프로세스에 비해 기억해야 할 정보의 양도 적고, 저장해야 할 메모리 영역도 줄어들어 성능상 이점이 있다.

말만 들었을 때 멀티 스레드는 굉장히 좋은 프로세스 관리 방식일 거라 생각이 들지만, 자원을 공유하는 특징에 대한 단점이 존재한다.

  • 자원을 공유한다는 것에는 여러 스레드가 하나의 자원에 접근할 때 동시성 이슈가 발생한다.

  • 자원을 정상적으로 동기화 하기 위해 락 매커니즘을 사용 했을 때 교착 상태가 발생할 수 있다.

  • 여러 스레드를 수행하며 왔다 갔다 하는 과정에서 컨텍스트 스위칭에 따른 오버헤드가 발생한다.

  • 스레드 하나라도 프로세스 내에서 오류를 내어 강제종료되면 이 프로세스도 강제종료된다.

스레드의 컨텍스트 스위칭

스레드는 OS 스케줄링에 의해 CPU 코어를 사용하여 논리적 흐름을 실행시킨다.

스레드가 짧은 연산을 마치고 다른 스레드가 연산을 하게 되는 경우 현재 진행 중이던 스레드는 일시 정지 되며 현재 상태의 정보를 백업 하고 있다가, 다시 자신의 차례가 되어 연산을 이어가게 되는 경우 백업해 둔 정보를 복원한다.

  • 스레드의 현재 상태를 백업하는 정보는 CPU 의 레지스터 값이다.

결론적으로 멀티 스레드를 "잘" 사용 하려면 공유하는 자원을 서로 쓰는 행위가 많지 않아야 동시성 이슈를 최소화할 수 있게된다.

멀티 스레드는 왜 필요할까?

CPU 의 발전으로 인해 다중 코어 환경에서 자원을 효율적으로 사용하기 위함이다.

초기에는 CPU 코어가 발전하면서 클럭 속도가 점차 증가하여 연산을 빠르게 할 수 있게 되었다. 하지만, 증가하다보니 더 이상 발전할 수 없는 한계에 봉착했다.

이 한계를 돌파하기 위한 묘수는 클럭 속도가 아닌 코어 갯수를 늘리는 것으로 선택 했고, 여러개의 코어가 뒷받침 되는 환경에서 더 효율적으로 쓸 수 있기 때문이다.

만약, 클럭 속도만 무수히 빠른 코어 1개 와 조금 느리지만 준수한 속도의 코어 여러개가 있다면 어떤 것을 선택할까?

클럭 속도가 빠르다고 무조건 빠를 것이라는 보장을 할 수없다. 컴퓨터라는 복합적인 기기 내부에는 다양한 주변기기가 존재하고, 그 주변기기 또한 연산을 필요로하기 때문이다.

화면에 그래픽을 표현 해주는데 메모리에 디스플레이 비트 값을 담고 있다가 뿌려줘야하고, 키보드 입/출력 또한 마찬가지인데 이러한 연산이 무수히 빠른 코어의 클럭속도를 따라가지 못할 만큼 느리기 때문이다.

  • 이로인해 단순한 작업임에도 불구하고 CPU 를 사용하기 위한 대기 시간이 발생하게 된다.

스레드 여러개를 사용하면 다른 스레드에서는 대기 시간이 긴 작업을 맡고, 또 다른 스레드에서 연산이 필요한 흐름을 이어갈 수 있다.

참고 자료


Last updated