"이 글은 10분 테코톡을 참고하여 만들었습니다."
우선 맨처음 이 글을 쓰게 된 계기는 OS와 spring 강의를 듣던 도중 쓰레드라는 용어가 자주 나오고, 하나의 프로세스에는 여러개의 쓰레드가 존재할 수 있어서, 여러가지 작업을 처리할 수 있다.. 까지는 이해하였지만,
도대체 프로세스 하나에 어떤 여러 작업을 처리한다는건지, 또 멀티프로세스 멀티코어는 뭔지 등등 의문점이 많이 생겨 쓰레드와 프로세스를 설명하기 위해서 이 글을 작성하게 되었다.
이 글을 작성하기 전에 제일 중요한 것은 용어입니다.
바로, 용어의 늪에 빠지지 않고 여러 단어들을 혼동해서 사용하지 않기 입니다.
왜냐하면 비슷한 의미를 가진듯한 단어가 굉장히 많이 나오기 때문입니다.(멀티 쓰레드, 멀티 프로세스.. 등등)
그래서 프로세스와 쓰레드의 용어는 간단하고 명료한데 위 용어들을 혼합해서 사용하면, 혼란스러움을 가져와 내가 대답을하다 길을 잃는 상황이 발생 할 수 있습니다.
실제로, 면접에서 대답을 하다가, 여러개의 단어를 혼동해서 사용하게 되면, 꼬리질문에 꼬리질문이 이어지다 털릴 수 있기 때문에, 정확히 용어에 대해서 인지하고, 대답을 하는것이 중요할 것 같습니다.
용어 정리
제가 제일 좋아하는 음식은, 치킨입니다.
불금에 치킨에 맥주를 마시려 BBQ에 갔는데 BBQ황금올리브를 주문을 시켰더니, 갑자기 치킨이 아니라 BBQ 황금올리브 레시피를 내놓는것입니다.
이것은 치킨이 아닙니다.
바로 이 레시피가 우리가 만든 코드파일, 있어보이는 단어로 프로그램이라고 합니다.
그래서 이 프로그램은 실행시키기 전에는 그저 코드가 구현되어있는 코드 파일일 뿐입니다.
마치 치킨레시피를 보고 만들어야 비로소 치킨이 되는것 처럼 말이죠.
그래서 이 치킨레시피가 치킨이 되는것처럼, 실행이 되어서 사용할 수 있는 무언가가 되야하는게 이것을 프로세스라고 합니다.
참고).
그렇다면 프로세스가 프로그램이 되면서 두가지 일이 일어나는데 그게 뭘까요?
code: 실행 명령을 포함하는 코드
data: stack변수 혹은 Global 변수
heap: 동적 메모리 입력
stack: 지역변수, 매개변수,반환값등의 일시적인 데이터
여기에 PCB에서는 여러가지 요소들이 있는데,
다시 프로세스로 돌아와서,
저는 게임을 할때 여러가지를 복합적으로 실행합니다.
예를 들어, 일단 게임을 하기위한 LOL 클라이언트를 키고요, 롤 매드무비 브금을 위해서 유튭music을 키고요, 또 친구랑 게임을 같이하기위해서 Discord를 키고요, 또 롤 룬특성을 바꾸기위해서 Op.gg를 킵니다.
휴 게임하나를 하는데도, 이렇게 많은 프로세스들이 동시에 실행 되어야합니다.
여기서 동시에는 아까 앞에서 뭐라고 했죠?
한순간에 여러일 일 일어나는게 아니라, 짧은 전환으로 여러가지 일을 동시에 처리하는것 처럼 보이는것 라고 했습니다.
고로, 원래 롤 프로세스를 실행시키기위해서 cpu를 주면 다른 프로세스는 ready상태가 되고,
cpu를 사용 할수가 없습니다. 그래서 롤을 하다가 유튭 뮤직을 들으려고 유튭 뮤직을 키면 롤이 꺼지게 되는것이죠.
그래서 다수의 프로세스를 동시에 실행하기 위해서 여러 프로세스를 시분할로, 즉 짧은 텀을 반복해서 전환하면서 실행시키게 됩니다.
그래서 processA가 cpu를 잡고 쓰다가, timer interrupt가 걸리던 IO를 하기 위해서 내놓으면, 그때서야 Ready상태에있던 processB가 cpu를 잡고 사용하다가 다시 Ready상태로 cpu를 내놓으면, 이제 그제서야 processA가 cpu를 잡고 사용하는것입니다.
이와중에 이렇게 processA와 processB가 cpu를 번갈아 가면서 쓰는것이 context switching이라고 합니다.
그래서 이렇게 번갈아서 쓰는게 비용이 너무 많이드니까 등장한것이 바로 경량화된 프로세스 버전인 쓰레드 입니다.
왜 경량화된것이냐 하면, 하나의 프로세스 안에 다수의 쓰레드가 있을 때 공유하는 자원이 있기 때문입니다.
쓰레드는 코드, 데이터, 힙 영역을 공통된 자원으로 사용을 하게 됩니다.
각 스레드는 스택 부분만을 따로 가지고 있게 됩니다.
공유되는 자원이 있기 때문에 효율적으로 사용이 가능하다고 하는것입니다.
쉽게 말해서 모조리 다 빼고 다시 다 넣을 필요가 없다는 말입니다.
이러면 캐싱 적중률이 올라갑니다.
예를 들어, 풋살장을 예약했다고 칩시다.
저희가 풋살장에 들어갈 타임이 되서 풋살장에 들어갔는데, 골대도 없고, 의자도 없고, 형광등도 없고, 아무것도 없는것입니다.
그이유가 프로세스의 컨텍스트 스위칭 같은 경우에는 어차피 다음 풋살장 이용 팀도, 골대와 의자 형광등도 다쓰는데, 그냥 모조리 챙겨서 나가 버린것이죠.
그러면 다음차례의 이용자인 제가 이것을 전부 다시 다 챙겨서 들어와야하는 대참사가 벌어지게 됩니다.
하지만 쓰레드의 컨텍스트 스위칭은 공용으로 사용할 것들은 두고 그냥 몸만와서 풋살장에서 즐겁게 풋살을 즐기면 되는것입니다.
훨씬 간단하고, 부담이 적습니다.
그러면 이제 앞에 내용을 이해했다면, 멀티 프로세스와 멀티 쓰레드에 대해서 이야기를 할 수 있습니다.
그전에, 먼저 설명할것이 멀티 프로세스와 멀티쓰레드 이 두가지 개념이 모두 처리방식의 일종이라는것 입니다.
그러니까 한 어플리케이션에 대한 처리방식이라고 생각하면 편합니다.
단순히 여러 프로세스를 띄워 놓은것을 멀티 프로세스라고 한다면(유튭,롤,디스코드 동시에 띄우기), 멀티 쓰레드랑 비교해서 이해하기가 힘듭니다.
고로 한 어플리케이션에 대해서 멀티프로세스 방식과 멀티 쓰레드 방식이라는 두가지 처리방식이 있다고 생각해야합니다.
쉽게 예시를 다시 들어보면, 유튜브를 틀고, 롤을하고, 디스코드를 하는 이렇게 다수의 프로세스를 실행하는것을=> 멀티프로세스 X
이게 아니라, 하나의 프로세스 웹브라우저(크롬)-> 하나의 프로세스인 크롬인데도, 여러개의 창을 띄운다던지, 아니면 유튭 Live방송을 보는데 Live 영상도 보여줌과 동시에, 실시간 댓글도 확인하고, 현재시간을 보여주는 시계도 보여주는 이러한 하나의 어플리케이션에 여러가지 처리가 필요한 경우를 뜻하는 것입니다.
Multi-process
만약에 여러명의 사용자가 로그인을 요청하는 상황이 있다고 가정해 봅시다.
하나의 프로세스는 하나의 로그인만 처리할 수 있기 떄문에 동시에 처리는 할 수 없습니다.
그래서 부모 프로세스가 fork()를 통해 자식 프로세스를 생성합니다.
이때 자식프로세스는 부모와 별개의 메모리 영역을 확보하게 되고, 이 자식프로세스가 다른 로그인을 처리하는 방식으로 여러개의 요청을 처리하게 됩니다.
Multi-thread
반면에 쓰레드는 한 프로세스 내에서 구분되어진 실행 단위입니다.
초반에 말했듯이 실행단위는 프로세스일 수 도 있고, 쓰레드일수도 있습니다.
만약에 프로세스가 다수의 프로세스로 구분되어있지 않다면, 단일 스레드 하나로 프로세스가 실행됩니다.->이때의 실행단위가 프로세스 그자체가 되는 것입니다.
만약에 프로세스 내에서 여러 쓰레드로 나뉘어져 실행단위가 나뉘어지면, 그게 멀티 쓰레드가 되는것입니다.
마치, 인텔리제이에서 코드를 작성함과 동시에, 인텔리 제이에서 자동으로 완성 추천 코드를 보여주는것 처럼 말입니다.
또는, 테스트 코드를 돌리는 동시에 소스코드를 수정해야 될때도 있습니다.
이처럼 한 어플리케이션에 대한 작업의단위가 나뉘어 질때가 많다.
이때의 각 쓰레드가 이 작업들을 담당하는 것입니다.
각 프로세스 처리방식의 특징
Multi-process
각 프로세스가 독립적
IPC를 통해서 통신을 해야함 -> 마치 같은 작업을 두명이 다른 회의실에서 하다가, 논의할 일이생기면 밖으로 나와서 이야기를 하고 다시들어가야하는것이다.
자원 소모적, 메모리 차지, 문맥교환 비용이 큼
동기화 작업이 필요하지 않음
Multi-thread
Thread끼리 긴밀하게 연결되어있음
공유된 자원으로 통신 비용 절감
공유된 자원으로 메모리가 효율적
문맥 교환 비용이 적음
공유자원 관리를 해줘야함
두명이 같은 회의실에서 작업을 처리하기 때문에 논의할 일이생기면 그냥 말만 걸면 된다.
그렇다면, Multi-Thread가 좋은건가?
이렇겐나 장점이 많아보이는데, 그럼 멀티 쓰레드를 사용하면 되는건가?
근데 사람들은 왜 멀티 프로세스를 사용하는 것일까?
예시를 들어보자면,
우리가 익스플로어로 티켓팅을 한다고 생각해보자, 여러개의 창을 띄울것인데
어떤창은, 시계창을, 어떤창은, 티켓팅창을, 어떤창은 공연장소에 해당하는 지도를,
또 어떤창은 계좌번호와 비밀번호를 까먹지말라고, 웹 메모장에 적어놨을수도있다.
나는 계좌번호와 비밀번호를 외우지못하고, 웹메모장에만 띄워놨는데, 갑자기 사진처럼
user가 너무 몰려서 뻑이 나버린것이다.
이러면, 문제가 뭐냐면, 이것은 멀티 쓰레드를 사용했기 때문에, 긴밀하게 연결되어 있어 한 쓰레드, 한 탭에 문제가 생기면, 전체 프로세스에 영향이가서, 전부가 죽어버리는 대참사가 발생하는 것입니다.
하지만 구글은 다소 비효율적일 수 있겠지만, 멀티 프로세서를 이용하기 때문에, 멀티 탭간의 영향을 덜받습니다.
그러면, CPU는 하나의 프로세스 밖에 처리를 하지 못하니까, 멀티코어가 아니면 멀티 프로세스를 실행할수 없는거냐 이런 생각을 가질 수 있는데,
멀티 코어는 조금 다른 관점에서 바라봐야합니다.
왜냐하면, 아직까지 이 글을 읽고있는 여러분은, 멀티 프로세스라 하면, 유튭과 롤을 같이 수행하는 멀티라는 단어 아직도 빠져 있기 때문이죠.
앞서 소개한 멀티 프로세스와 멀티 쓰레드는 하나의 어플리케이션에 해당하는 처리방식의 일종이기 때문에, 소프트 웨어 분야에 가깝고, 멀티코어는 조금 더 하드웨어의 측면에 가깝기 때문입니다.
고로, 이런 싱글 코어로도, 멀티 프로세스 처리가 가능합니다.
어떻게 할까요? 바로 원펀맨의 분신술처럼,
그냥 한명의 원펀맨인데, 엄첨 빨리 뛰어서 여러명의 원펀맨이 만들어진것처럼 사용하면됩니다.
CPU의 연산속도가 엄청나게 빠르기 때문에, 시분할로 쪼개서, 하나의 프로세스를 수행하다가 또 다른 프로세스를 수행하고, 또 cpu를 뺏었다가 다른 프로세스를 실행하는 작업을 반복하면됩니다.
그러면, 마치 싱글 코어임에도, 여러 프로세스가 동시에 수행되는듯한 착각을 일으키게 하는것이죠.
하지만, 멀티코어는 병렬처리, 즉 물리적으로 여러 코어를 사용해서 다수의 실행단위를 한순간에 처리할 수 있게 해준것입니다.
그래서 여러개의 코어에서 각각 하나의 코어마다 실행단위가 진행 될 수 있게 되는것입니다.
이게 바로 병렬처리입니다.
다수의 프로세서로 여러가지 일을 각 코어에서 진행시키게 하는것이죠
번외). 리눅스에서 Process와 Thread
리눅스는 프로세스와 쓰레드를 동일하게 봅니다.
여기서 그러면, 리눅스에는 뭐 아에 쓰레드가 없다는건지, 스레드를 만들면 프로세스가 그냥 된다는건지 이해하기 어렵다.
그래서 이 문장을 리눅스 커널에서는 프로세스와 스레드를 동일하게 본다. 라고 이해해야한다.
쓰레드는 User 쓰레드와 커널 쓰레드로 나뉘는데,
커널 쓰레드 -> 쓰레드가 여러개 있다는 사실을 운영체제 커널이 알고 있다. 하나의 쓰레드에서 다른 쓰레드로 넘어갈때 CPU 스케쥴링 하듯이 넘겨준다.
USER 쓰레드 => 프로세스안에 쓰레드가 여러개 있다는걸 운영체제는 모르고, 라이브러리의 지원을 받아서 관리한다.
그래서 운영체제는 쓰레드가 여러개 있는지를 모르고, 프로세스 내부에서 CPU수행단위를 여러개 두고 이용하므로, 여러 제약들이 있다.
리눅스에서는 사용자 스레드 하나당 커널 스레드 하나가 매칭되어있는 것입니다.
그래서 리눅스 커널 입장에서 보면 각각의 쓰레드가 하나의 프로세스다 라고 표현을 하는 것입니다.
그런데 이 프로세스가 살짝 특이한 점이 있다.
프로세스는 각각 다른메모리를 가지고 고유의 pid를 가지고 있는데.
여기서는 메모리를 공유를 합니다.=>그래서 light weight process라고 부릅니다.
그러면 리눅스에서 생성된 스레드는 각각 다른 pid를 가지고 있을까요?
그건 아닙니다.
한 프로세스에서 생성된 스레드는 모두 같은 pid를 가지고 있습니다.
그러나 tgid라는 스레드 그룹아이디가 존재하고, tid라는 쓰레드 아이디 라는것도 있습니다.
그래서 User Level에서 보면 이 쓰레드 그룹아이디가 PID로 보여지게 되고,
커널 입장에서 보면 Tid가 pid로 인식이 됩니다.
그래서 마치 커널에서는 하나의 동일한 프로세스에서 만들어진 여러개의 쓰레드라 할지라도,
쓰레드를 마치 다른 별개의 프로세스처럼 PID가 각각 다른 쓰레드로 인식하여, 각각 다른 프로세스로 보이게 되는것입니다.
요약