0. Intro
Rx에서 제공하는 스레드(AndroidSchedulers.MainThread와 같은 편한 그 무언가...)를 자연스럽고 편하게 사용하고 있을 찰나, 익숙함에 속지 않지 위해 정리하고자 하는 글. 운영체제에서 말하는 process와 thread부터 시작한다.
1. Process
Process: An instance of a program in execution
- 프로세스(Process): 실행중인 프로그램
운영체제 위에서 연속적으로 실행되고 있는 프로그램을 의미한다. 프로그램은 실행할 수 있는 물리적인 파일을 말하는 것이고, 우리가 이 프로그램을 실행시키면 프로세스 인스턴스가 생성된다. 인스턴스가 생성된다는 말은 즉 메모리에 적재된다는 뜻이다.
인텔리제이도 실행하고 카톡도 실행하고 구글 브라우저도 실행시키는 상황을 생각해보자. 이 각각의 프로그램들은 각각의 프로세스를 갖고 있다. 프로세스는 프로그램 실행시 코드(code), 데이터(data), 스택(stack), 힙(heap)의 구조로 되어 있는 각각 독립된 메모리 공간을 할당받고 독립적으로 실행된다. 때문에 첫 번째 프로세스에서 무언가 문제가 발생하였다면 첫 번째 프로세스만 죽게된다. 애플리케이션을 쓰다가 문제가 생기면 그 애플리케이션만 강제종료가 되는 것처럼, 죽어버린다고 말할 수 있다.
또한 CPU가 작업하는데 필요한 자원(시간)을 프로세스간에 나누는 멀티 프로세싱(multi-processing) 덕분에 여러 애플리케이션을 동시에 작업할 수 있는 것이다. 위 이미지의 프로세스는 동시에 실행되고 있는 것 같지만, CPU는 한 번에 한 가지의 일밖에 처리하지 못하기 때문에 프로세스들을 번갈아가면서 실행한다. CPU에서 동작중인 프로세스가 대기하면서 해당 프로세스의 상태(context)를 보관하고, 대기하고 있던 다음 순번의 프로세스가 동작하면서 이전에 보관했던 프로세스의 상태를 복구하게 된다. 이렇게 진행 중인 작업을 바꾸는 것을 context switching 이라고 하는데 이 과정이 굉장히 잘게 빠르게 돌아가기 때문에 사람들에게는 이 프로세스들이 마치 동시에 진행되는 것처럼 느껴지게 된다.
1-1) multi-processing
어떤 작업을 하나 이상의 프로세스에서 여러 개의 프로세서가 병렬적으로 처리하는 것을 멀티 프로세싱(multi-processing)이라고 한다. 하나의 애플리케이션이 갖고 있는 일을 여러 프로세스에 일을 나누어 줌으로써 하나의 작업을 처리하도록 하는 것이다. 각자 메모리 영역을 가지고 있고 독립적으로 동작하기에 여러 개의 자식 프로세스 중 하나에 문제가 발생해도 다른 자식 프로세스에 영향을 주지 않는다. 하지만 그만큼 Context Switching이 많아지고 성능저하가 일어날 수도 있다.
2. Thread
Thread: the subset of a process and is also known as the lightweight process.
- 스레드(Thread): 단어처럼 하나의 가닥, 맥락 즉 하나의 프로세스 내에서 실행되는 실행 단위
스레드가 없으면 프로세스가 돌아갈수 없기 때문에 기본적으로 프로세스당 최소 1개의 스레드를 가지고 있고 그것을 메인 스레드(main thread)라고 한다. 메인 스레드 이외에도 한 프로세스는 여러 개의 스레드를 가질 수 있는데 다수의 스레드는 자원을 공유하며 관리하기 때문에 효율적으로 작동할 수 있다. 그래서 경량화된 프로세스라고도 불린다.
2-1) multi-threading
하나의 프로세스는 멀티 스레딩(multi-threading)을 지원한다. 일반적으로 멀티 스레드를 사용하는 이유는 단일 스레드로 처리하기 어려운 네트워크처리나 DB와 같은 비동기처리를 해줘야하기 때문이다. 프로세스 안에서의 스레드는 저마다 해야 되는 업무를 배정받는다. 예를 들어 Thread1에서는 네트워크 작업을, Thread2에서는 유저에게 보일 UI 작업을 할 수 있다.
하나의 프로세스 안에서 동작하는 스레드들은 프로세스의 메모리 공간을 공유할 수 있다. 스레드는 저마다 필요한 일들을 수행하게 되고 스레드는 자기들만의 수행해야 되는 함수의 호출을 기억해야 되기 때문에 스레드마다 스택(stack)이 할당되어져 있다. 하지만 이 프로세스 안에서 동작하는 스레드들은 결국은 한 프로그램을 위해서 일해야 되므로 프로세스에 지정된 코드(code)와 데이터(data) 힙(heap)들에 공통적으로 접근해서 공통적으로 업데이트가 가능하다. 공유되는 자원이 있기 때문에 stack 영역만 처리를 하면 되는 것이다.
이처럼 스레드간 데이터를 주고 받는 것이 간단해지고 처리 비용이 감소하게 된다. 또한 멀티 프로세싱에서 발생했던 context switching의 오버헤드에 대한 부분도 해소된다. context switching이 일어날 때 stack 영역만 처리하면 되기 때문에 caching 적중률이 올라가고 캐쉬 메모리를 초기화할 필요가 없어지게 되는 것이다.
그리고 이런 스레드는 동시다발적으로 발생할 수 있기 때문에 이 프로세스가 조금 더 효율적으로 일을 할 수 있도록 도움을 준다. 만약 프로세스가 하나의 일 밖에 하지 못한다면 음악을 듣는 동안 사진 편집을 할 수 없을것이다. 이렇게 스레드들이 프로세스 안에서 각각의 일을 하고 있기 때문에 다양한 일들을 동시에 할 수가 있는 것이다.
스레드가 동시다발적으로 일을 하기에 효율적인 점도 있지만 치명적인 위험부담도 따른다. 스레드들은 자신들이 일을 할 때 어디서부터 어디까지 일을 했고 그 다음엔 어디로 가야 하는지 일의 흐름을 기억할 수 있는 고유의 stack이 지정되어져있다고 했다. 데이터나 코드나 힙 같은 공통적인 데이터 리소스는 프로세스에 있기 때문에 스레드들은 이 프로세스에 공통적으로 할당된 리소스에 동시다발적으로 접속해서 서로 공유하면서 사용을 한다. 그래서 여러 스레드가 동시에 작업을 하다보면 공유된 데이터를 서로 사용하다가 충돌이 일어날 가능성이 크다. 그렇기에 여러 스레드를 관리하는 것은 까다로운일이다..!
3. 안드로이드 Process와 Thread
안드로이드의 Process와 Thread는 앞서 정리한 운영체제에서의 프로세스와 스레드와 다르지 않다. 하지만 안드로이드가 제공하는 컴포넌트들과의 관계까지 함께 이해해야 안드로이드 OS가 원하는 방향과 앱이 어떻게 동작하는지 알 수 있다.
안드로이드 앱을 처음 실행하면 Android OS는 메인 메모리에 하나의 쓰레드를 가진 하나의 Linux 프로세스 를 올린다. 이 때의 스레드를 메인 스레드라고 부른다. 또한 기본적으로 메인 액티비티를 비롯한 모든 컴포넌트(4대 컴포넌트인 Activity,Service,BroadCast,Receiver)는 단일 프로세스 및 메인 스레드에서 실행된다.
이 컴포넌트들 사이에서 해당 애플리케이션의 프로세스가 이미 존재할 경우 해당 구성 요소는 같은 프로세스 내에서 시작되고 같은 실행 스레드를 사용한다. 하지만 우리는 컴포넌트별로 각 별도의 프로세스에서 실행되도록 할 수 있고 어느 프로세스에서 추가 스레드를 만들 수도 있다. 매니페스트 항목에서 android:process 특성을 설정하여 특정 구성 요소를 제외한 일부 구성 요소만 프로세스를 공유하게 할 수도 있고, 같은 Linux 사용자 ID를 공유하고 있다면 다른 애플리케이션의 구성 요소를 동일한 프로세스에서 실행시킬 수도 있다.
3-1) Android Process Hierarchy
안드로이드의 제한된 메모리 관리는 언제나 관건이다. 안드로이드 OS는 프로세스에 우선순위를 정하고 유지할 프로세스와 종료할 프로세스를 결정하며 메모리를 효율적으로 관리한다. 예를 들어 메모리가 부족하면 오랫동안 사용하지 않은 프로세스나 중요하지 않은 중요도가 낮은 프로세스들을 먼저 제거하여 메모리를 확보하는 것이다. 앞서 안드로이드 컴포넌트들은 공통의 프로세스를 사용할 수도 있다고 했는데 그 프로세스에서 돌아가고 있는 컴포넌트들의 상태에 의해 프로세스를 종료할지 말지 결정한다.
중요 계층에는 다섯 가지 단계가 있는데 다음은 중요도에 따른 이미지이다. 큰 흐름을 잡는 정도의 간략한 설명과 함께 보자.
- 포그라운드 프로세스(Foreground Process)
현재 앱이 실행되고 있을 때 사용자와 인터랙팅하고 있는 액티비티가 가장 중요할 것이다. 만약 각 실행되고 있는 액티비티가 Service 컴포넌트에 의존하고 있거나 BroadcastReceiver가 onReceive 상태라면 이 또한 같은 포그라운드 프로세스에서 작동되고 같은 계층으로 속할 것이다.
- 가시적 프로세스 (Visible Process)
현재 실행되는 액티비티가 보이지만 foreground 영역에 속해있지 않은 경우이다. 예를 들어 포그라운드에 실행되고 있는 액티비티에서 새로운 Dialog를 띄웠을 때 액티비티는 보이지만 onPause()가 호출된 상태이고 사실상 포그라운드에서 실행되고 있는 것은 Dialog 일 것이다.
- 서비스 프로세스 (Service Process)
위 두 영역에 속하지 않고, 서비스 컴포넌트를 실행했다면 이 서비스 프로세스에 속한다고 할 수 있다. 화면에 보이는 인터페이스없이 계속되어야 하는 처리를 담당하는 프로세스이다.
- 백그라운드 프로세스 (Background Process)
백그라운드에 실행되고 있는 데이터로 사용자에게 보여주지 않고 존재하는 상태이다. 네트워크 데이터 다운로드나 오랫동안 실행되고 있는 서비스는 이와 같은 계층에 속한다.
- 빈 프로세스 (Empty Process)
프로세스 내 동작하는 액티비티 또는 서비스가 없는 경우 백그라운드 프로세스와 마찬가지로 프로그램을 강제로 종료하여 메모리를 회수한다.
3-2) main thread, worker thread
안드로이드의 스레드는 크게 1개의 메인 스레드에서 모든 안드로이드 컴포넌트가 기본적으로 실행되며 각각의 컴포넌트에 대한 시스템 호출은 메인스레드로 전달된다.
안드로이드의 메인 스레드는 다음과 같은 특징과 제약사항이 있다.
- 화면의 UI를 그리는 처리를 담당한다.
- 안드로이드 UI 툴킷의 구성 요소와 상호작용하고, UI 이벤트를 사용자에게 응답하는 스레드이다.
- UI 이벤트 및 작업에 대해 수 초 내에 응답하지 않으면 안드로이드 시스템은 ANR(Application Not Responding, 응용 프로그램이 응답하지 않음) 팝업창을 표시함. 따라서 시간이 오래 걸리는 작업은 새로운 스레드를 생성해서 처리해야 함.
메인스레드가 일정시간 블록될경우 ANR이 발생하기에 네트워크등 별도의 작업은 백그라운드 스레드(background thread)로 작업을 처리해줘야한다. 가장 간단히 사용할 수 있는 것은 Runnable 객체를 만들어서 새로운 백그라운드 스레드를 만들어주는 것이나 AsyncTask를 이용하는 것이지만 현재 deprecated되었다. 이 부분에 대해서는 Rx를 이용하여 비동기처리하는 포스팅으로 to be continued..!