✨ 오늘 공부한 것
- 알고리즘 카펫 풀이 - 코틀린 문법 종합반 5주차 복습 - 프로세스, 스레드, 코루틴 학습
나는 부끄럽지만 CS 지식이 없다. 이전에 스터디도 해보고, 책도 읽어봤지만 머리에 남아있는 건 뭐가 있지..?ㅠ 사실 지금은 코틀린을 이용한 안드로이드 앱 개발에 대해 배우고 있어서 CS를 공부할 시기가 아니긴 한데, 자주 나오는 단어들을 이해 없이 넘어가다 보면 나중에 다칠 것 같아 알아보았다. 자세한 개념들은 적당히 넘겨가면서 코틀린의 스레드와 코루틴을 이해할 수 있을 정도로만 공부해보았다.
메모리란 무엇일까. 보통 RAM(Random Access Memory)이라고 부르며, 단기적으로 데이터를 저장하고, 접근할 수 있는 공간을 제공한다. 더 자세히 타고 들어가면 뭐 주기억장치는 ROM과 RAM으로 나뉘고, ROM은 비휘발성인데 RAM은 휘발성이고… 등등이 있겠지만, 오늘은 CS를 공부하는 것이 아니니까! 정말 간단하게만 정리하겠다.
메모리는 단기적으로 데이터를 저장하기 때문에 프로그램 파일 같은 걸 저장하는 용도가 아니다. 그저 CPU가 작업할 수 있도록 하는 공간을 제공하는 것이다. 여기서 말하는 CPU의 작업으로는 프로그램 실행이나 인터넷 검색 등의 흔히 우리가 “컴퓨터를 사용한다”고 말하는 작업들을 말한다. 이 RAM의 용량이 커야 인터넷 창 여러 개 띄워 놓고 안드로이드 에뮬레이터 여러 개를 띄워 놔도 렉이 안걸린다.
프로그램을 실행하면, 혹은 객체를 생성하면 메모리에 올라가게 되고, 이걸 메모리에 “로드(load)”된다고 말한다. 프로그램을 냅다 메모리에 올리는 게 아니라, 실행에 필요한 부분, 정적인 변수 부분, 함수를 호출할 부분 등으로 나눠 각 부분마다 메모리 공간을 할당하게 된다. 나누는 이유는 CPU가 작은 메모리에 더 빨리 접근할 수 있기 때문이라고 한다.
그럼 드디어 메모리가 어떤 식으로 구성되어 있는지 알아보자.
메모리는 위 그림과 같이 크게 4가지 영역으로 나눌 수 있다. 하나하나 알아보자.
Stack Overflow와 Heap Overflow
그림을 다시 보면 힙 영역과 스택 영역은 각각 위에서, 아래에서 부터 차곡차곡 쌓이는 구조이다. 이 둘은 사실 같은 공간을 공유하는데, 각 영역이 상대 영역을 침범하는 경우 Heap Overflow, Stack Overflow라고 한다. 스택 오버플로우를 마냥 지식인 사이트로만 알았었는데, 힙 영역을 침범했던 것이었구나..
그냥 메모리 구조만 알아서는 안된다. 사실 JVM 메모리 구조만 알아도 되긴 하다.. 아무튼 코틀린은 JVM 위에서 돌아간다. 그리고 JVM은 자바 바이트 코드를 기계가 실행할 수 있는 형태로 변경해준다. JVM은 운영체제로부터 메모리를 할당받고, 코틀린의 클래스들은 그림에 나온 JAVA 컴파일러 대신 코틀린 컴파일러를 통해 바이트 코드로 변경되어 JVM의 메모리 영역인 Runtime Data Area에 적재된다.
메모리 구조와 비슷하니 간단하게만 알아보자.
코틀린은 모든 자료형이 참조형인데?
이 전에 자료형에 대해 잠깐 정리했을 때, 코틀린은 모든 자료형이 참조형 자료형이라고 했다. 그런데 참조형 자료형은 메모리 해제가 필요한 힙 영역에 저장된다. 모든 변수를 참조형으로 다루면 메모리 누수가 쉽게 발생하는 것 아닌가? 라고 생각해서 찾아보았다.
코틀린의 자료형은 컴파일 시 Java의 기본형에 맞는 자료형이 있다면 그것에 맞게 변환해준다고 한다. 예를 들어 Int면 Java의 int형으로 변환되는 것이다! 따라서 걱정할 필요는 없다~
스레드라는 녀석을 알려면 또 프로세스를 알아야 한다. 프로세스는 쉽게 말해 실행 중인 프로그램의 단위이다. 보통 작업 관리자의 프로세스 탭에 있는 프로그램 각각이 프로세스라고 할 수 있다.
위에 작성한 메모리의 구조에서, 프로그램은 메모리에 로드되어야 실행된다고 했다. 또 메모리에 로드되는 것은 메모리 공간을 할당 받는 것으로도 이해할 수 있다. 프로그램 하나 당 위에서 말했던 메모리 구조 하나씩 할당된다.
그런데 메모리 공간을 할당 받았다고 해서 프로그램을 실행할 수 있는 게 아니다. 그저 저장하는 것 뿐.. CPU가 해당 메모리에 접근해서 코드를 실행시켜야 프로그램이 실행된다.
메모리 공간과 (메모리에 접근하고 코드를 실행시키는) CPU의 시간 등을 CPU 자원이라고 하고, 프로그램은 이런 CPU 자원을 할당 받아 프로세스가 된다.
프로그램은 코드 덩어리고 프로세스는 실제로 실행되는 프로그램이라는 것까지는 이해했다. 그렇다면 크롬을 여러 개 실행하게 되면 어떻게 될까? 작업 관리자에는 크롬 프로세스가 여러 개 쌓이게 된다. 즉, 프로그램 하나는 여러 개의 프로세스를 가질 수 있는 것이다.
드디어 스레드에 대해 정리할 수 있다. 하나의 프로세스는 하나의 (스택, 힙, 코드, 데이터로 구성된) 메모리 공간을 할당받는다. 따라서 한 번에 하나의 작업 만을 수행할 수 있다.
그런데 우리는 하나의 크롬을 실행시키더라도, 파일을 다운로드 받으면서 유튜브를 보면서 웹 서핑도 할 수 있다. 바로 스레드 덕분에 가능한 일이다.
스레드는 한 프로세스 내에서 동시에 진행될 수 있는 작업의 단위이다. 스레드를 단어 그대로 “실”로 표현해 하나의 실행 흐름, 또는 실행 가닥이라고 표현하는 경우도 있다. (사실 내가 그렇게 배웠음)
프로그램과 프로세스의 관계처럼, 하나의 프로세스는 여러 개의 스레드를 가질 수 있다. 이를 멀티 스레드라고 한다.
메인 스레드?
프로세스를 생성하면 기본적으로 메인 스레드 하나가 생성된다. 이외의 스레드는 개발자가 직접 만들어주어야 하는 것이다.. (가질 수 있다고 했지 자동은 아니라고 했다.) 다시 정리해보면 프로세스는 메인 스레드를 포함해 적어도 하나의 스레드를 갖고 있고, 여러 개 존재하는 것을 멀티 스레드라고 한다.
프로세스는 하나의 독립된 메모리 공간을 할당받기 때문에 다른 프로세스의 메모리에 직접 접근은 불가능하다. 그러나 스레드는 프로세스의 자원을 공유해서 사용하기 때문에 다른 스레드의 정보에 접근할 수 있다.
정확히 말하자면 스레드는 프로세스의 자원 중 메모리의 스택 영역을 할당 받아 복사해 각각의 독립된 스택 영역을 갖고 있게 된다. 이 외에 코드, 데이터, 힙 영역은 서로 공유해서 사용한다.
코틀린의 스레드도 JVM 위에서 동작한다는 것만 빼면 동일하다. 단, JVM의 Heap 영역과 Method 영역은 공유하고, 나머지 Stack Area, PC Register, Native Method Stack은 개별로 생성한다.
정말 멀리 돌아온 느낌이다. 단어를 보면 Co + Routine의 형태이다. 즉, 협력하여 작업을 실행하자라는 의미가 있는 코루틴은 프로세스에서의 스레드처럼 동시 처리를 위해 사용되며, 스레드보다도 더 작은 작업 단위이다. 스레드 하나 당 여러 개의 코루틴을 가질 수 있다. 구글에서는 이 코루틴의 사용을 적!극! 권장한다고 한다.
하나의 스레드 안에서 여러 개의 코루틴 객체를 만들어 각각의 작업에 객체를 할당하는 방식으로 동작한다. 코틀린의 경우, 코루틴 객체이기 때문에 JVM Heap에 적재된다고 한다. 그렇다면, 스레드에 관계 없이 실행된다는 것으로 이해하면 되지 않을까? 실제로, 특정 스레드에 속하지 않고 한 스레드에서 중단된 루틴이 다른 스레드에 의해 재실행될 수 있다고 한다.
사람의 뇌는 한 번에 한 가지 일 밖에 처리하지 못한다. 멀티 태스킹을 잘 하는 사람은 사실 동시에 여러가지를 하는 것이 아니라, 빠르게 집중하는 곳을 바로바로 바꿔 동시에 하는 것처럼 보이게 되는 것이다.
CPU도 그렇다. 사실 CPU의 “코어”가 그렇다. 코어 하나 당 하나의 작업 만을 처리할 수 있기 때문에 여러 작업을 조금씩 나눠 번갈아가면서 실행하게 되고, 이걸 시분할 기법이라고 한다. 이 때, 실행하는 작업을 바꾸는 것을 Context Switching 이라고 한다.
💡 코어가 여러 개라면?
각 코어에 프로세스와 스레드를 돌려 여러 작업을 정말 동시에 수행할 수 있다. 이는 병렬성(Parallelism) 프로그래밍이라고 한다. 멀티 코어 프로세서에서 가능한 일이라고 한다.
동시성Concurrency | 병렬성Parallelism |
---|---|
동시에 실행되는 것처럼 보이는 것 | 실제로 동시에 실행되는 것 |
논리적인 개념 | 물리적인 개념 |
싱글 코어, 멀티 코어에서 가능 | 멀티 코어에서만 가능 |
스레드와 코루틴의 동시성을 보장하기 위한 방식을 알아보기 전에, 블로킹과 논블로킹에 대해 대충 알아보고 넘어가자. 참고할 것은 Block이 차단 혹은 대기라는 뜻을 가진다는 것이고, 제어권은 자신의 코드를 실행할 권리로 이해하면 되겠다.
운영체제 커널에 의한 컨텍스트 스위칭을 통해 동시성을 보장한다.
소스 코드를 통한 Programmer Switching으로 동시성을 보장한다. 코드를 통해 스위칭 시점을 마음대로 정할 수 있다.
스레드와 코루틴은 각자의 방법으로 동시성을 보장하기 위한 기술이다. 코루틴은 스레드를 대체하기 위한 수단이 아닌, 스레드를 더 효율적으로 쓰기 위한 수단이라고 한다. 차이점을 정리해보자.
스레드 | 코루틴 | |
---|---|---|
작업의 단위 | Thread | Coroutine Object |
동시성 보장 수단 | Context Switching | Programmer Switching |
다른 작업의 결과를 기다릴 때 | Blocking | Suspend |
사실 관련된 강의는 어제 들었지만, 추가로 알아야 할 게 많은 것 같아 오늘 알아보면서 정리했다. 정리하다 보니 개념에 대해 어느 정도 이해는 한 것 같기도 한데 이게 또 코드를 작성하려고 하면 헷갈릴 것 같다.
그래도 개념만 알아둔다면 비동기 처리는 쉽게 잘 만들어 놓은 라이브러리 갖다가 쓰지 않을까 하는 행복 회로를 돌리는 중이다. 아니라면 유감인거지 뭐~