본문 바로가기
안드로이드/자바

스레드(Thread)란?

by 나이아카 2022. 8. 26.

 얼마전에 여행 관련해서 꽤 괜찮은 회사의 면접을 진행했었습니다. 예전 면접들과는 다르게 나름 준비도 했다면 했고, 이제 어느정도 경력도 쌓여 면접관의 질문에 정확하게는 아니더라도(단어나 그 순서 등이) 내가 알고 있다는 것 정도는 어필할 수 있는 수준으로 대답할 수 있을 것이라는 묘한 자신감도 조금 있었습니다. 그러나 면접은 신기한게 준비를 하면 할수록 더더욱 특이한 곳에서 터지고, 그로 인해 결국 제대로 대답하지 못하고 망하는 경우가 많았습니다. 이번에도 마찬가지로 분명 알고 있는 내용임에도 불구하고 횡설수설 한다던가, 용어가 기억이 안나 모른다고 대답하고 말아서 준비가 덜 된 것이 아닌가 하는 생각이 듭니다. 아무래도 아직도 기초가 부족한 모양이라, 하나하나 기록해둬야겠습니다.


 이번 글의 주제는 스레드입니다. 이 스레드에 대해서 알기 전에 미리 알고 가야 할 단어가 존재합니다. context-switch라고 불리는 것인데, 이는 스케쥴러가 기존의 실행 프로세스를 실행 우선순위 및 다른 이유로 인해 다른 프로세스로 교체해야 할 때 프로세스의 상태값을 교체하는 것을 의미합니다.(이는 프로세스 기준이며 프로세스를 스레드 및 코루틴으로도 변경 가능합니다.)

 스레드는 프로세스의 자원을 공유하며 프로세스 내부에서 처리해야 할 작업들을 스택과 레지스터를 이용해 처리해주는 유닛을 의미합니다. 이러한 스레드를 가진 프로세스는 미리 개발자가 만들어둔 '프로그램'을 메모리 상에 올려 실행중인 것을 의미합니다.

 이를 한 번에 이어서 정리하면, 개발자가 만들어 낸 '프로그램'을 실행시키면 메모리 상에 프로그램이 실행되고 그것을 '프로세스'라 부릅니다. 이러한 프로세스 내부에는 1개 이상의 '스레드'가 존재해 프로세스 내부에서 처리해야 하는 작업을 처리하게 됩니다.

 그리고 2개 이상의 '스레드'가 프로세스 내부에 존재하는 것을 멀티 스레드 프로세스라고 부릅니다.

 공부를 하다 보니 이러한 스레드는 왜 등장하게 되었는지가 궁금해졌습니다. 스레드의 역사를 잠깐 살펴보니 1965년에 버클리 시분할 시스템에서 처음 개념이 등장했다고 합니다. 하지만 이때는 스레드가 아니라 프로세스로 불렸다고 하네요. 그리고 시간이 지나 유닉스가 등장했고, 유닉스에서는 프로세스가 '순차적인 task 처리' 및 '가상주소 공간 사용' 등의 특징을 지니고 있었으며 이로 인해 기존의 프로세스와 다르게 각 프로세스끼리의 메모리를 공유하지 않아 각 프로세스의 통신을 위해서는 추가적인 과정을 거치게 되었습니다. 이는 하나의 프로세스 내부에서 데이터를 공유해 동시에 여러가지 일을 처리하기 위한 스레드를 등장시키게 되었습니다.

 이러한 스레드의 장점으로는 순차 실행이 아닌, 동시 실행(병렬성)이 가능해짐으로서 멀티코어 시스템에서 병렬 처리가 가능해졌다는 점입니다. 또한 하나의 프로세스 내에 존재하는 여러 스레드들끼리는 메모리가 공유되기 때문에 데이터를 이동하기 위해 별도의 추가적인 리소스가 들지 않습니다.(프로세스의 경우 각각 메모리를 사용하기 때문에 각 데이터를 이동하기 위해서는 여러가지 방법을 통해 통신 연결을 해야 하는 소모값이 있습니다.) 또한 프로세스의 context-switch 보다 스레드의 context-switch이 더 빠른 속도로 이루어지기 때문에 추가적인 리소스가 적게 듭니다.

 여담으로 안드로이드에서 UIThread라 불리는 메인스레드는 안드로이드 프레임워크의 main()함수의 호출과 동시에 실행됩니다.

 만약 프로세스가 하나의 스레드를 지니고 있다면, 이는 위에서 설명한 동시 실행 및 메모리 공유와 관련된 기능의 의미가 퇴색됩니다. 그러나 2개 이상의 스레드를 지닌 프로세스와 비교해 context-switch가 일어나지 않고 task를 순차적으로 처리하기 때문에 단일 task를 처리하는데 있어 매우 빠른 속도를 보장할 수 있습니다. 하지만 여러개의 CPU를 활용할 수 없고(CPU의 코어가 1개라면 단일 스레드가 최적의 성능을 낼 수도 있지만) 결국 프로세스형태이기 때문에 스레드의 의미가 퇴색될 수 있습니다.

 이와 달리 프로세스가 2개 이상의 스레드를 지니고 있다면 위에서 설명했던 것처럼 멀티스레드 프로세스라고 부르는데, 이때에도 2가지 방향에 따라 작동 방식이 조금 달라지게 됩니다. 먼저 CPU가 스레드 개수보다 많은 여러개의 코어를 지니고 있는 경우에는 각 스레드가 동시에 실행되어 각 task의 시간만큼 동작하고 끝내게 됩니다. 이때에는 가장 오래 걸리는 task 만큼의 시간만에 모든 task가 마무리됩니다. 그러나 CPU의 코어가 1개라면 각 스레드는 하나의 코어를 가지고 context-switch를 통해 마치 여러개가 동시에 동작하는 것 처럼, 각 task를 번갈아가며 실행하게 됩니다. 이때 총 시간은 context-switch 시간을 제외하면 각 task의 총 합과 동일합니다. 멀티스레드를 이용하는 경우에는 가장 중요한 것이 os가 멀티스레드를 지원해주어야 한다는 것이고, concurrency한 상황이 왔을 때 스레드 스케쥴링을 어떤 방식으로 잡느냐 하는 문제가 또 다시 발생합니다.

 위의 사진에서 concurrency(동시성)이 하나의 코어에서 2개의 스레드가 동작하는 방식이고, parallel(병렬성)이 2개의 코어에서 2개의 스레드가 동작하는 방식을 표현하는 것입니다.

 이렇듯 스레드는 하나의 프로세스 내에서 공유 데이터를 가지고 여러 task를 통시에 처리하고자 함이 가장 큰 목적이고, 이를 위해 공간을 분할해 놓은 것이라고 생각하면 좋을 것 같습니다.


 사실 스레드의 정의보다 Looper, Handler, MessageQueue에 대해서 정리를 하려고 했으나 하다보니 이번 글과는 맥락상 다른 분야인 듯 해서 두 개의 글로 분리하기로 했습니다.(덕택에 뭔가 이번 글이 허겁지겁 끝나는 느낌이 드네요.)

글을 쓰다보면 항상 배워야 할 주제가 더 많아지는 것을 느낍니다.(그리고 다 쓰고 나면 까먹죠) 블로그에 올릴 수 있는 제가 아는 주제가 늘어난다는 건 언제나 기분 좋은 일이더라구요. 제 블로그가 더 다채로운 주제로 차오를 수 있게 노력해봐야겠습니다.

댓글