<Coroutine> 1. 코루틴 이란?
by BFine참고 & 출처 : https://www.yes24.com/Product/Goods/125014350
가. JVM
a. 동기 & 비동기
- 동기와 비동기의 순서(시간)와 관련이 있다. 동기는 앞선 작업이 끝날때까지 기다리는 것이고 비동기는 앞선 작업과 상관없이 실행되는 것을 의미한다.
=> 주의할점은 비동기에는 멀티스레드가 필요해보이지만 비동기는 작업의 순서에만 관련이 있기 때문에 이론적으로 스레드가 1개여도 가능하다.
b. 예제
- 별도의 처리를 하지않으면 메인스레드 하나만 동작한다. 그리고 메인프로세스가 종료되면 JVM 프로세스도 같이 종료된다.
- 비동기 처리를 위한 가장 간단한 방법은 Thread를 생성하여 처리할 수 있다.
- 위의 예시에서 볼 수 있듯이 메인스레드가 종료되더라도 다른 스레드가 존재하는 경우 JVM 프로세스는 종료되지않는다.
- JVM은 스레드를 우선도가 높은 사용자 스레드와 우선도가 낮은 데몬 스레드로 구분하고 사용자 스레드가 모두 종료될때 JVM이 종료된다.
c. 스레드는 왜 비싸다고 하지?
- 직접 스레드를 만드는 경우 두가지 문제가 있다.
1. 스레드는 생성과 수거 비용이 비싸기 때문에 직접 사용하게 되는 경우 무분별하게 만들면 자원을 많이 소모하게 된다.
2. 스레드 관리를 개발자가 직접해야하기 떄문에 복잡성 증가와 잘못 사용시 메모리릭을 유발할 수 있다.
- 스레드의 생성/수거 비용이 비싼이유는 먼저 스레드는 고유의 스택 메모리를 가져야하는데 이를 OS에서 직접 한다
=> 스레드를 위한 컨텍스트(레지스터, 프로그램 카운터 등) 생성을 필요로 한다.
- 추가로 각종 오버헤드가 발생하는데 스레드 수거의 경우 OS 스케쥴러가 목록에서 해당 스레드를 제거하고 다시 스케쥴링 해야하는데 오버헤드가 발생한다.
d. Executer 프레임워크
- 위의 개발자가 스레드를 직접 다뤄야하는 문제를 해결하기 위해 Executer 프레임워크를 제공한다.
=> 이 프레임워크는 스레드의 재사용성을 높이고 관리 포인트를 맡기기 위해 만들어졌다.
- 프로그래밍에서 자원을 많이 사용하지만 특성을 가지지 않아도 된다면 보통 미리 여러개를 만들고 그것들을 돌려서 사용하는 부분이 많다.
- 스레드도 마찬가지로 생성/수거 비용이 비싸니 매번 새로 만들지 않고 미리 만들어놓고 사용하면 되는데 이 방법을 스레드풀이라고 부른다.
- Executer 프레임워크에는 스레드 관리를 위해 스레드풀을 사용하여 스레드들을 관리 하며 작업을 분배하는 역할을 한다.
=> 스레드가 작업을 끝내더라도 스레드를 종료하지 않고 다음 작업이 들어오면 재사용한다.
- 이렇게 Executer 프레임워크를 사용하면 스레드풀에 속한 스레드 관리(생성/수거) 작업분배의 책임을 개발자가 하지 않아도 된다.
- 위의 예제에서 볼수 있듯 스레드 두개를 Executors의 스레드풀에 생성하고 해당 스레드들이 5개의 작업을 처리하는 것을 볼 수 있다.
=> Executors 내부에는 작업 대기열을 관리하는 BlockingQueue를 가지고 있기 때문에 스레드 개수 이상의 작업을 받을 수 있다.
d. Executor 프레임워크의 한계
- 문제 없어보이지만 Executor 프레임워크는 대표적으로 스레드 블로킹 문제를 가지고 있다.
- 여러 스레드가 동기화로 하나만 접근 해야하거나 동시접근을 제한하기 위한 뮤텍스나 세마포어로 인해 제한 되는 경우에도 블로킹이 발생한다.
- 추가로 다른 스레드의 작업의 결과가 필요한 경우 해당 스레드의 작업이 끝날때까지 대기해야하므로 비싼 스레드자원을 사용할수 없게 된다.
- 특정 스레드에 작업이 몰릴수도 있어 다른 스레드들은 놀고 있기 때문에 낭비가 발생할수 있어 실무에서는 ForkJoinPool을 주로 쓰는 걸로 보인다.
e. 기존 멀티 스레드 프로그래밍에 한계
- ForkJoinPool & CompletableFuture를 사용하더라도 여전히 Thread를 사용하기 때문에 여전히 한계점은 남아있다.
- 콜백을 사용하거나 체이닝함수를 사용하는 등 으로 스레드 블로킹을 피할수 있지만 작업 간의 종속성이 복잡해질수록 스레드 블로킹을 피하기 어렵다.
- CompletebleFuture의 체이닝을 통해서 비동기 결과를 응답받아 처리하는 부분을 구현할수 있다. 여기서는 메인메서드가 다른 작업 할수 있는 장점이 있다.
- 그러나 여전히 1번 작업과 2번 작업은 독립적으로 처리 할수 있지만 1번 작업을 기다려야 2번 작업을 처리할수 있다..
나. Coroutine (코루틴)
a. 무엇인가
- 비동기 작업을 보다 효율적으로 처리하기 위한 경량 스레드의 한 부분으로 볼 수 있는 라이브러리 이다.
b. 대체 뭐가 가볍다는 건데?
- 코루틴은 스레드와는 다르게 OS 커널 스레드가 아닌 사용자 수준의 스레드로 동작하기 때문에 하나의 스레드 내에서 수많은 코루틴을 만들수 있다.
- JVM 내에서 사용자 수준의 스레드로 관리되기 때문에 컨텍스트 스위칭 비용이 거의 없어 일반 스레드보다 비용측면에서 이점을 가지고 있다.
=> 그렇기 때문에 프로세스와 스레드 관계와 스레드와 코루틴 관계랑 비슷하다고 볼수 있다.
- JVM의 스레드는 JVM 스케쥴러에 의해서 관리가 되지만 실제로 OS 커널 스레드와 연결되어 실행 된다.
- OS 커널 스레드와 JVM 스레드 그림 참고
=> https://coderstower.com/2021/04/05/java-concurrency-thread-pools-and-executors/
- JVM 스레드와 코루틴 그림 참고
=> https://www.stefankreidel.io/blog/kotlin-coroutines-async
다. Coroutine (코루틴) 만들기
a. kotlinx.coroutines
- 코틀린에서 코루틴을 지원해주긴하지만 저수준 API 만 제공해서 사용하기에 한계가 있기 때문에 Jetbrains 에서 만든 라이브러리를 사용한다.
dependencies {
...
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:버전")
...
}
b. runBlocking
- runBlocking 함수는 해당 함수를 호출한 스레드를 사용해 실행되는 코루틴을 만들어 내며 생성된 코루틴이 실행 완료될때까지 블로킹한다.
=> 호출한 스레드를 점유하는 코루틴을 만들어내고 메인스레드의 점유가 종료되고 프로세스가 종료된다.
c. launch
- runBlocking 함수의 람다식에서는 CoroutinScope에 접근할 수 있고 CoroutinScope의 확장함수인 launch 를 사용할 수 있다.
- launch 함수는 코루틴을 추가로 생성할수 있으며 CoroutineName을 통해 이름도 지정해줄 수 있다.
- 총 3개의 코루틴이 생겼으며 메인스레드를 점유하고 있고 launch 밖에 있는 코루틴이 먼저 실행되며 나머지 코루틴은 순차적으로 실행되었다.
d. suspend
- suspend 키워드는 일시중단 지점을 포함할 수 있는 기능을 제공한다. 예시에서 볼 수 있듯이 블로킹이 발생하면 다른 코루틴이 실행된다.
=> 주의할점은 suspend 함수는 코루틴을 생성하지 않기 때문에 launch나 async 등을 사용해서 코루틴을 생성해주어야한다.
- 위의 예시처럼 suspend 키워드가 있어야 코루틴의 확장함수를 사용할 수 있다.
- suspend 함수가 아닌 함수를 실행하면 블로킹이 발생해도 다른 코루틴이 실행되지 않는다.
- 유의할점은 suspend 키워드를 쓰더라도 내부적으로 일반 함수를 사용하면 여전히 블로킹이 발생해도 다른 코루틴이 실행되지않는다.
- 위와 같이 withContext를 사용해서 코루틴 디스패쳐의 스레드에서 처리하도록 설정해주어야 다른 코루틴이 실행되는것을 볼 수 있다.
=> 결국 withContext 는 스레드기반으로 동작하기 때문에 유의해서 사용해야 할 것으로 보인다.
'공부 > Coroutine' 카테고리의 다른 글
<Coroutine> 6. 일시 중단 함수와 코루틴 멀티스레드 (0) | 2024.07.07 |
---|---|
<Coroutine> 5. 코루틴의 예외처리 (0) | 2024.06.30 |
<Coroutine> 4. 구조화된 코루틴 (2) | 2024.06.30 |
<Coroutine> 3. 코루틴 컨텍스트(Coroutine Context) (0) | 2024.06.22 |
<Coroutine> 2. CoroutineDispatcher와 CoroutineBuilder 그리고 async (2) | 2024.06.16 |
블로그의 정보
57개월 BackEnd
BFine