[컴퓨터 이야기] 프로세스와 스레드의 개념과 차이



오랜만이에요. 거의 한두달 꼴로 블로그에 글을 쓰게 되네요. 요즘 부족한 공부들을 복습하면서 노션에 따로 적고 있어서 블로그에는 글을 쓸 시간이 많지 않았는데, 그래도 공부했던 내용을 블로그에 정리하는 게 나을 것 같다고 생각해서 여기에 적습니다. 

오늘은 학부 단골 수업 내용이자 단골 면접 질문인 프로세스와 스레드의 개념과 차이에 대해서 학습해보고자 합니다. 너무 쉽고, 당연하고, 뻔한 내용일 수도 있겠지만 막상 누가 물어보면 정확하게 설명하긴 힘들더군요. 한번 알아봅시다. 프로세스와 스레드, 그리고 프로그램이 뭘까요?


프로그램

파일이 저장 장치에 저장되어 있지만 메모리에는 올라가 있지 않은 정적인 상태입니다. 우리가 웹사이트나 마켓 스토어 같은 곳에서 애플리케이션을 설치하게 되는데 그것이 프로그램이죠. 프로그램을 설치하면 바로 실행이 될까요? 아닙니다. 마우스로 더블 클릭하거나 손으로 터치를 해야 메모리에 올라가면서 실행이 되는 것이죠. 이렇게 아직 실행이 되지 않은, 메모리에 올라가지 않은 정적인 상태가 프로그램입니다. 


프로세스와 멀티 프로세스

프로세스

운영체제로부터 자원을 할당받은 작업의 단위입니다. 위에서 프로그램은 실행되지 않고 단순히 설치되어 있는 상태라고 이야기했는데, 프로세스는 이제 마우스로 더블 클릭을 하든, 앱을 터치를 하든 해서 실행 상태에 진입한 것을 이야기합니다. 실행이 되면 메모리에 해당 프로그램이 운영체제로부터 돌아가기 위한 자원 및 영역을 할당받게 되며 동적인 상태가 됩니다. 이 때 메모리에 적재될 때의 메모리의 영역에는 code, data, heap 등의 영역이 존재하게 됩니다. 

https://s3.ap-northeast-2.amazonaws.com/lucas-image.codesquad.kr/1625800634797Screen Shot 2021-07-09 at 12.14.43 PM.png

TEXT 또는 Code 영역 : 실행할 프로그램의 코드가 저장되는 영역입니다. CPU는 해당 영역에서 코드를 불러와서 명령어를 실행하고 처리하게 되죠.

GVAR, BSS 또는 data 영역 : 프로그램의 전역 변수가 담기는 영역입니다. 데이터 영역은 프로그램이 시작할 때 할당이 되었다가 프로그램이 종료되면 소멸이 됩니다. 이렇게해야 전역적으로 사용할 수 있는 변수를 저장하고 유지할 수 있을테니까요.

HEAP, 힙 영역 : 사용자가 직접 관리할 수 있는 영역입니다. new 등을 이용해서 동적으로 할당된 데이터가 여기에 저장됩니다. 이 때 JAVA같은 언어는 가비지 콜렉터 (Garbage Collector, GC) 가 있는데, 동적으로 할당된 데이터가 참조가 끊어지게 되면 GC가 쓰레기가 된 그 데이터를 수거해 갑니다. 그림에서 보시다시피 낮은 곳에서 높은 곳으로 주소 방향이 할당됩니다.

STACK, 스택 영역 : 지역 변수와 매개 변수가 저장되는 영역입니다. 스택은 함수의 호출이 발생할 때 할당되고, 함수의 코드가 전부 실행되면 소멸됩니다. 이렇게 스택 영역에 저장되는 함수의 호출 정보를 스택 프레임 (Stack Frame) 이라고도 부릅니다. 우리가 흔히 보는 Stack Overflow는 너무 많은 함수가 호출되어서 스택이 꽉차게 될 때 발생하게 됩니다. 스택 영역은 메모리 상에서 제한된 영역만 가지고 있는데, 자꾸 호출하면서 스택 영역에 쑤셔넣으니까 아파하는 것이죠. 특히 재귀 함수에서 탈출 조건을 제대로 걸지 않거나, 제한된 조건을 가지고 있다면 이 Stack Overflow를 볼 수 있게 됩니다.


멀티 프로세스

스레드를 이야기하기 전에 멀티 프로세스부터 이야기해 봅시다. 멀티 프로세스는 무엇일까요? 게임을 한다고 생각해봅시다. 게임 속에서 어떤 작업을 하게 될까요? 옛날에는 매우 단순한 형태의 게임만 존재했지만, 요즘은 게임을 하면서 게임 속의 노래를 들으면서, 다른 유저와 채팅을 하는 등 하나의 응용 프로그램에서도 여러 가지 동작을 하게 됩니다.

그럼 이 작업을 하나의 프로세스가 모두 처리하게 될까요? 가능합니다. 애당초 옛날에도 이런 식으로 구현하기도 했고, 이론 상 그냥 하나의 프로세스가 전부 처리하는 것이 문제가 되진 않죠. 그런데 작업을 하다가 어떤 작업에서 문제가 발생해서 해당 프로세스가 죽어버리면 어떻게 될까요? 당연히 다른 작업에도 영향을 끼치고, 심할 경우 프로세스가 중지될 것입니다. 그리고 하나의 프로세스가 모든 작업을 하게 되면, 당연히 속도가 무척 느려질 것입니다.


그렇기 때문에 프로세스를 여러 개로 나눠서 일을 나눠서 하게 만드는 것이죠. 하나의 작업이라도 여러 개의 프로세스가 달라붙어서 협업을 한다면 금방 끝나겠죠? 

스타크래프트라는 게임을 예로 들어보겠습니다. (스타크래프트를 모르는 분들은 죄송합니다. 적절한 예시가 떠오르지 않아서..) 당신의 목표가 마린 12명 만들기이고, 마린 1명을 뽑는데 1초가 걸린다면, 하나의 배럭에서 모든 마린을 뽑아내면 시간이 얼마나 걸릴까요? 당연히 12초가 걸립니다. 그런데 배럭이 4개가 있고 여기서 마린을 뽑기 시작한다면? 3초만에 12명을 뽑을 수 있습니다.

여기서 배력 = 프로세스라고 한다면, 1개의 배럭에서 뽑는 것이 단일 프로세싱, 4개의 배럭에서 뽑는 것이 멀티 프로세싱이 되겠죠. 여기서 중요한 것은, 각 배럭은 각자 마린을 몇명을 뽑았는지, 또는 배럭을 만들 때 얼마만큼의 미네랄이 소모되었는지 다른 배럭들과 공유하지 않는다는 것입니다. (애당초 어디서 몇 명의 마린을 생산했는지 같은 정보는 중요하지 않지만..) 즉 프로세스는 고유의 메모리 영역을 할당받았기 때문에, 각자의 Code, Heap, Stack 등의 영역 역시 공유하고 있지 않습니다.





스레드와 멀티 스레드

스레드

멀티 프로세스에서의 문제점은, 각자 고유의 메모리 영역을 할당받았기 때문에 다른 프로세스의 영역에 접근할 수 없다는 것입니다. 아니, 방법이 있긴 합니다. IPC (Inter-Process Communication)나 공유 메모리 등을 사용해서 프로세스 간 통신을 지원할 수 있긴 합니다. 대신 CPU 레지스터 교체 및 RAM과 CPU 사이의 캐시 메모리까지 리셋되어버려 자원 부담이 큽니다. 게다가 작업량이 많으면 Context Switching이 발생하는 과정 속에서 오버헤드가 발생할 수 있습니다. 결국 협업이 필요한 상황에서 다른 자원의 접근이 용이하고, Context Switching에서의 오버헤드도 적게 하는 다른 방법이 필요하게 됩니다.

스레드는 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위입니다. 쉽게 말해서 프로세스에 붙어서 프로세스의 작업을 작업을 도와주는 좀 더 작고 세부적인 단위입니다. 이렇게 프로세스에 달라붙는다면 어떤 일이 일어나는가 하면, 달라붙은 숙주 프로세스의 자원을 같이 사용하게 됩니다. 위에서 이야기한 메모리 구조 중 Stack 영역을 제외하고 나머지 영역을 공유하게 됩니다. Stack 영역은 프로세스 내에서 따로 할당 받죠. 스레드가 프로세스의 안에서 동작하는 녀석이라고 했는데, 하나의 프로세스에는 여러 개의 스레드가 들어갈 수 있습니다. 그렇기 때문에 각 스레드의 자원이 결국 프로세스의 자원이기 때문에 공유가 가능하며 쉽게 공유할 수 있게 됩니다. 또한 자원을 공유하기 때문에 멀티 프로세스가 고유의 메모리 영역을 차지하는 것에 비해 경제적이죠. 

다시 스타크래프트로 넘어갑시다. 팩토리나 사이언스 패실리티, 커맨드 센터는 옆에 붙일 수 있는 애드온이라는 것이 존재합니다. 애드온은 각 주 건물의 옆에 붙어서 주건물에서 생산하는 유닛과 관련된 스킬들을 연구할 수 있게 합니다. 만약 이 스킬을 팩토리 같은 주 건물이 해야한다면? 유닛을 생산하기 전에 스킬을 배우거나, 스킬을 배우기 전에 유닛을 생산해야 합니다. 즉 주 건물이 해야하는 일을 대신 해주는 것이죠. 이 때 에드온은 주 건물에 동력이 들어와야 일을 할 수 있습니다. (아쉽게도 스타크래프트로는 멀티 스레드를 설명할 수 없네요.)

여기서 주 건물 = 프로세스, 에드온 = 스레드일 때, 에드온은 주 건물의 유닛 정보를 공유 및 프로세스 동력 정보 등을 공유하게 됩니다. 프로세스가 없으면 당연히 스레드도 돌아가지 않죠. 즉, 스레드는 프로세스 내에서 프로세스의 자원을 공유받아 프로세스가 할 일을 해주는 녀석입니다.


멀티 스레드
위에서 거의 설명했지만, 하나의 프로세스는 여러 개의 스레드를 가질 수 있고, 이를 통해 작업을 나눠서 진행할 수 있게 만드는 방법이 멀티 스레드입니다. 멀티 스레드는 하나의 프로세스 자원을 공유하기 때문에, 고유의 자원을 가지고 작업을 하는 멀티 프로세스보다 훨씬 경제적입니다. 
그렇다면 무조건적으로 멀티 스레드가 더 좋을까요? 그건 아닙니다. 멀티 스레드는 하나의 영역을 공유하기 때문에 하나의 스레드에서 문제가 발생하면 해당 프로세스가 종료되어 버립니다. 위에서 이야기했던 프로세스 내에서 작업 중에 문제가 생긴다면 프로세스 전체가 죽는다고 이야기했었죠? 그게 이 이야기입니다. 대신 멀티 프로세스는 하나의 프로세스에서 문제가 발생한다고 해도 다른 프로세스에게까지 영향을 주진 않습니다. 또한 자원을 공유하기 때문에 동기화의 문제가 발생할 수 있습니다. 어떤 스레드가 먼저 접근할지 모르니 작업 진행 중에 다른 스레드에 의해 값이 바뀌어버리는 결과가 발생할 수 있다는 이야기죠. 이것을 해결할 수 있는 것이 뮤텍스 (Mutex)세마포어 (Semaphore) 입니다.


뮤텍스

열쇠로 따고 들어가야 되는 단일 화장실과 같은 존재입니다. 해당 화장실에 들어가기 위해선 한 개밖에 없는 키를 받아야하죠. 이 키는 당연히 사람이 가지고 있으며, 여기서 화장실 = 공유 자원, 사람 = 프로세스 or 스레드가 됩니다. 즉 프로세스 또는 스레드가 하나의 키를 가지고 공유 자원을 사용하고 있으며, 이 키를 가지고 있지 않는 스레드 및 프로세스는 공유 자원 사용이 빌 때까지 대기해야 합니다.


세마포어 (Semaphore)

여러 개의 칸막이가 있는 화장실이라고 볼 수 있습니다. 칸막이에 들어가기 위해선 그냥 들어가서 잠그면 됩니다. 그럼 ‘사용 중’ 이라는 문구가 나오면서 사용할 수 없게 되는데, 여기서 각 칸막이는 공유 자원을 사용할 수 있는 일종의 좌석? 이라고 보면 될 것입니다. 즉 화장실이라는 큰 공유자원을 칸막이로 나눠서 공유할 수 있게 되며, 이는 즉 여러 프로세스 및 스레드가 하나의 공유자원을 나눠 쓸 수 있다는 이야기죠. 이 때 사용 중인 스레드 및 프로세스는 사용 가능 공간을 -1 해서 카운트를 업데이트하여 자리가 꽉 차면 공유자원을 사용하기 위해 대기 상태에 들어갈 수 밖에 없게 만듭니다.






마치며..

어차피 잘 정리된 글은 다른 블로그에서 찾아볼 수 있고 저는 그냥 이야기하듯이 한 번 적어봤습니다. 하나 설명하려면 다른 개념을 설명해야 되다보니 글이 길어졌네요. 뭔가 보충 설명이 있거나 틀린 내용이 있다면 댓글로 달아주세요. 다른 분들이 볼 수 있게 도와주시면 좋을 것 같습니다. 그럼 감사합니다!








댓글