동시성에 대하여...feat 코루틴
앱을 막힘없이 효율적으로 동작시키기 위해서는 어떻게 해야할까요?
사람이 물체를 인지하는 과정은 눈이 보내는 신호를 뇌가 받아들이는 구조입니다. 그리고 빠르게 보여지는 연속적인 이미지를 인식하는데에는 한계가 있습니다. 보통 사람은 초당 60프레임의 연속된 움직임과 그 이상의 프레임의 움직임의 차이를 느끼지 못한다고 합니다.
1000ms / 60frames = 16.666..ms/frame
따라서, 사용자가 스마트폰의 화면에서 움직임이 부드럽다고 느끼도록 하기 위해서는 16ms마다 한 프레임을 그리는 작업(통신, 렌더링, 입력 ,처리)을 완료해야합니다.
하지만, 앱에 필요한 적업 중에서 16ms보다 긴 처리 시간을 요구하는 작업(네트워크 통신, 데이터베이스 작업, JSON 파싱) 등이 있습니다. 다. 이 긴 작업이 메인 스레드에서 처리된다면 화면이 느리거나 ANR이 발생할 수 있습니다. 그러므로 안드로이드 개발에서는 UI Thread(메인 스레드)에서 최소한의 작업을 하고, Worker Thread(백그라운드 스레드)에서 긴 작업을 처리해야 합니다.
그렇다면, Worker Thread에서 단순히 오래 걸리는 작업을 처리하면 효율적일까요?
회원 정보, 뉴스 기사, 날씨 정보 총 3개의 네트워크 통신을 해서 한 화면에 정보를 표시해야한다고 가정해보겠습니다. 만약 회원 정보를 서버에서 가져온 후에 뉴스 기사 정보를 가져오고, 그 후에 날씨 정보를 가져온다면 화면에 정보를 표시하는데 상당히 오랜시간이 소요될 것입니다. 예시와 같이 오래걸리는작업들을 효율적으로 처리하기 위해 비동기 처리가 필요합니다.
비동기(Asynchronous) 처리를 통해 개발자는 여러 작업을 수행할 때 앞서 실행 했던 작업의 끝마침 여부와 상관없이 다음 작업이 수행할 수 있습니다. 따라서, 안드로이드 프로그래밍에서는 Worker Thread에서 비동기 처리를 통해 긴 작업을 수행하고, 작업이 완료되면 UI Thread에 구현해 놓은 결과를 파라미터로 하는 callback 함수를 통해서 화면을 업데이트 하게 됩니다.
병렬성 vs 동시성
처리해야 할 작업 A와 B 두 개가 있다고 가정해봅시다.
1. 작업을 처리할 수 있는 코어의 수가 두 개 이상인 경우
처리 작업을 진행할 수 있는 코어가 두 개가 있다면, A 작업과 B 작업을 동시에 진행하여 빠른 속도로 작업을 처리할 수 있습니다. 이것이 병렬성 프로그래밍 입니다.
2. 작업을 처리할 수 있는 코어의 수가 두 개 미만인 경우
처리 작업을 진행할 수 있는 코어가 한 개라면, 병렬성 프로그래밍과 같이 두 개의 작업을 동시에 진행할 수 없습니다. 따라서 A 작업을 진행하다가 suspend 등으로 기다려야 하는 시간에 B 작업을 진행하다가 A 작업이 재개되면 다시 돌아와 A 작업을 진행하여 두 작업이 동시적으로 진행되는 것처럼 보이게 처리하는 하는 것을 동시성 프로그래밍 이라고 합니다. 그리고 코루틴(Corountine)은 이러한 동시성 프로그래밍을 지원하는 설계 패턴입니다.
코루틴(Corountine)
코루틴은 Lightweight Thread 라고도 부릅니다. 작업 하나하나에 Thread 를 할당하는 것이 아닌 'Object' 를 할당해주고, 이 Object 를 자유롭게 스위칭함으로써 Context Switching 비용을 대폭 줄이고 작업을 효율적으로 분배하여 동시성을 보장합니다.
코루틴의 특징
- Dispacher(코루틴이 실행을 특정 스레드 풀로 전달)를 간단히 설정 할 수 있음
- suspend 키워드를 만난 Kotlin 컴파일러는 내부적으로 콜백 코드를 생성하여, 코루틴은 반복적인 들여쓰기로 인한 콜백 기반 코드를 순순차적인 코드로 작성할 수 있어서 가독성 증가 및 코드를 단순화 할 수 있음
- 스레드에 비해 적은 생성 비용, 특정 스레드에 종속되지 않음
- 여러 개의 코루틴을 실행한다고 했을 때 코루튼 1을 실행하던 중에 Context Switching 으로 스레드를 바꾸며 코루틴 2를 실행하는 것이 아니라 기존 스레드 위에서 코루틴 1을 정지시키고 코루틴2를 실행한다. 이러한 방식으로 빠른 연산 및 메모리 비용 측면에서 효율적임
- 코루틴의 수명을 제한하는 특정 Scope(GlobalScope, CoroutineScope, ViewModelScope, LifecycleScope)를 통해서 메모리 누수를 감소시킴
* 프로세스 : 운영체제로부터 자원을 할당받은 작업의 단위.
* 스레드 : 프로세스가 할당받은 자원을 이용하는 실행 흐름의 단위