# 코루틴 개념 자체는 1963년도에 처음 나옴 : https://en.wikipedia.org/wiki/Coroutine 코루틴은 비동기 비차단 코드를 작성할 수 있는 경량 스레드 ## 예제 소스코드 ```bash= git clone https://github.com/afashs/coroutineEx.git ``` ### 루틴(Routine)과 서브루틴(Subroutine) `루틴(Routine)`은 컴퓨터 프로그램에서 하나의 정리된 task이다. 프로그램은 보통 크고 작은 루틴을 결합하여 만든다. 루틴은 다시 메인 루틴(Main Routine)과 서브루틴(Subroutine)으로 나뉜다. 메인 루틴(Main Routine) : 프로그램 전체의 개괄적인 동작 절차를 표시하는 루틴이다. 서브루틴(Subroutine) : 반복되는 특정 기능을 모아놓고 여기에 이름을 붙여 놓은 것이다. `코틀린에서는 함수가 대표적인 서브루틴의 예시이다.` 코루틴(Coroutine, Cooperative Routine)도 루틴의 일종이다. 다만 이를 서브루틴과 비교했을 때 몇가지 차이점이 있는데, - 코루틴은 메인-서브 관계가 없다. 즉 모든 코루틴이 서로를 호출할 수 있다. - 서브루틴은 진입 지점과 탈출 지점(return)이 각각 하나씩만 존재하지만, 코루틴은 내부적으로 여러개의 진입 지점과 탈출 지점을 가질 수 있다. -> 중간에 멈춰서 특정 위치로 갔다가 다시 돌아와서 나머지 일을 할수 있다. - 서브루틴은 메인 루틴에 종속적이지만 코루틴은 메인 루틴에 종속적이지 않아 대등하게 데이터를 주고받을 수 있다. - 코루틴은 특정 스레드에 바인딩되지 않음. 한 스레드에서 실행을 일시 중지하고 다른 스레드에서 다시 시작할 수 있다 (다만 코루틴 일시 중단은 코루틴 스코프 내부에서 수행되어야 한다) ### 비선점형 멀티태스킹(Non-preemptive multitasking)과 선점형 멀티태스킹(Preemptive multitasking) 특정 Task가 CPU의 사용권을 얻었을 때, 이 사용권을 강제로 뺏을 수 없으면 비선점형 멀티태스킹, 뺏을 수 있으면 선점형 멀티태스킹이다. 코틀린에서 `코루틴은 비선점형 멀티태스킹`이며 `쓰레드는 선점형 멀티태스킹`이다. -> 코틀린은 동시성(Concurrency)을 제공하지만 병렬성(Parallelism)을 제공하지 않는다. 동시성(Concurrency) : 하나의 Thread에서 여러 Task가 동시에 실행되는 것처럼 보이게 하는 것 (대부분은 시분할로 동시성을 구현한다). 병렬성(Parallelism) : 여러 Task가 동시에 실행되는 것처럼 보이게 하기 위해 여러개의 Thread를 사용 CPU가 제공하는 Thread의 수는 제한되어 있다. 그럼 병렬성을 구현할 때 Thread 개수가 초과되면? 운영체제에서 Context Switching 기법을 이용해 스레드를 교체한다(이것도 시분할을 많이 사용) 근데 이 Context Switching은 생각보다 비용이 많이 드는 작업이다 <p align="center"> <img width="400"src="https://i.imgur.com/TKjaQpd.png"/> </p> > Single Thread로 동시성을 제공하는 코루틴은 이 부분에서 성능적으로 이득이 될 가능성이 높다. 여러 Thread의 동기화를 위한 Mutex, Semaphore 기법 등을 코루틴에서는 사용할 필요가 없다. > 결론적으로 코루틴은 여러 Task가 동시성을 구현하면서도 > 스레드보다 비용이 적은 멀티태스킹 방식이라고 할 수 있다. ### Blocking 과 Suspending BLOCKING : 함수B를 수행하기 위해서는 함수A가 먼저 끝나야 한다. <p align="center"> <img width="400"src="https://i.imgur.com/RSpA0jF.png"/> </p> SUSPENDING: 함수A가 시작 된 후 정지(suspended) 될 수 있으며, 함수B가 수행되고 끝난 뒤 함수A가 재개(resume)될 수 있다. 즉 이 쓰레드는 함수A에 의해 블락되지 않는다. <p align="center"> <img width="400"src="https://i.imgur.com/29ktYu1.png"/> </p> ### CoroutineContext <p align="center"> <img width="500"src="https://i.imgur.com/iVeI1G4.png"/> </p> CoroutineContext는 Job, CoroutineName, CorotuineDispatcher 등과 같은 Element 인스턴스를 순서가 있는 `Set(indexed set)`으로 관리 한다. 특이한 점은 각 Element 또한 CoroutineContext 이기 때문에 컬렉션 내에 있는 모든 element들 또한 컬렉션이 된다. 그리고 모든 element에는 이를 식별하기 위한 고유한 Key를 가지고 있다. 이 부분이 `구조화된 동시성(Structured Concurrency)`을 구현하기 위한 기반 요소가 된다. ### Job - Job 자체적으로 생명 주기를 갖는다 (시작 -> 실행중 -> 종료) - 다른 Job 과 1:n 의 부모-자식 관계를 맺어 최상위 Job 부터 최하위 Job 까지의 계층 구조 - 기본적으로는 취소 가능하며 부모 Job 의 취소는 자식 Job 의 취소로 이어짐 - 코루틴 자체도 하나의 Job이다 (CoroutineContext[Job] = this). > **자식 Job 의 오류로 인한 종료는 부모 Job 이 취소되도록 만듭니다.** (자식 Job 이 취소로 인해 종료되면 부모 Job 을 취소하지 않습니다). Job 의 생명 주기와 상태 별 동작 특성 <p align="center"> <img width="600"src="https://i.imgur.com/KCbn4Mt.png"/> </p> ### CoroutineDispatcher 디스패처는 코루틴을 어떤 문맥 에서 실행할지 결정 문맥은 코루틴 제어규칙을 미리 몇가지로 나누어서 번들로 제공함 - Dispatchers.Default 기본 문맥인 CommonPool에서 실행되기 때문에 새로운 스레드를 생성하지 않고 기존에 있는 것을 이용한다. 따라서 연산 중심의 코드에 적합하다. - Dispatchers.IO 입출력에 적합한 공유 풀로써, 블로킹 동작이 많은 파일 or 소켓 I/O 처리에 사용하면 좋다. - Dispatchers.Uncofined 첫번째 중단점을 만날때까지만 호출자 스레드에서 실행된다. 중단점 이후에 재개될 때 서스펜드 함수가 실행된 스레드에서 수행된다. - newSingleThreadContext 사용자가 직접 새 스레드 풀을 만들 수 있다. 새 스레드를 만들기 때문에 비용이 많이 들고 더 이상 필요하지 않으면 해제하거나 종료시켜야 한다. ### 구조화된 동시성 structured concurrency https://kotlinlang.org/docs/coroutines-basics.html#structured-concurrency - 모든 코루틴은 CoroutineScope 상에서 시작된다 - 하위 CoroutineScope 가 완료될 때까지 상위 코루틴은 완료될 수 없다. ## 동기화 제어 https://kotlinlang.org/docs/shared-mutable-state-and-concurrency.html <p align="center"> <img width="500"src="https://i.imgur.com/GDS1g0b.png"/> </p> 각 쓰레드는 JVM의 Stack 영역을 차지하고, 코루틴은 각각의 작업이 Object로 할당되어 JVM의 Heap에 적재된다. ### 동기화 상태 제어 방법 종류 자바의 volatile는 일단 안됨 쓰레드가 여러개가 아님 - synchronized 특정 스레드가 이미 자원을 사용하는 중이면 나머지 스레드의 접근을 막아 데이터의 안정성을 보장 - Atomic Variable 원자 변수란 특정 변수의 증가나 감소, 더하기나 빼기가 단일 기계어 명령으로 수행되는 것을 말한다. (해당 연산이 수행되는 도중에는 누구도 방해하지 못하기 때문에 값의 무결성을 보장) - Mutex 상호 배제는 코드가 임계 구역(Critical Section)에 있는 경우 절대로 동시성이 일어나지 않게 하고 하나의 루틴만 접근하는 것을 보장 - Actor 코루틴의 결합으로 만든 actor는 코루틴과 채널에서 통신하거나 상태를 관리 - 쓰레드 가두기(SingleThreadContext) https://sungje365.tistory.com/33 ## 비동기 데이터 스트림 데이터 스트림을 계산하는 경우 Sequences를 사용해 결과를 나타낼 수 있다. 그런데 메인 쓰레드를 블록킹 하기 때문에 비동기에 대해 대응 불가 ## race condition 이나 데드락을 피하기 위해서 ### synchronization 아래부터는 비동기를 대응하기 위해 코틀린 내부에서 코루틴이 사용됨 https://kotlinlang.org/docs/flow.html ### Suspend funcition 비동기로 수행되는 연산을 수행한 후 한 개의 값을 반환 ### Flow suspending function은 비동기로 단일값을 반환하는 반면, Flow는 비동기로 동작하면서 여러 개의 값을 반환가능 cold stream (kotlin의 sequence)으로 동작하며 hot stream 은 지원하지 않는다.(Hot은 채널 쓰면 됨) 데이터는 요청할 때마다 처음부터 새로 발행되며, 요청 전에는 선언만 있을 뿐 아무런 동작도 하지 않는다. - emit : 송신 - collect : 수신 ### Channel channel은 FIFO 방식의 queue 형태로 구현되어 있기 때문에 sequential한 접근을 보장해 동기화 이슈를 해결한다. Channel 은 여러 방향에서 데이터를 던지고 받는 형식으로 코루틴 끼리의 데이터를 전달 스트림에 밀어 넣을 땐 send, 스트림에서 받을 땐 receive ![](https://i.imgur.com/0kk5UJh.gif =600x) ### Actor 동기화 이슈가 있는 자원을 actor 내에서 관리하도록 하며, actor 클래스의 멤버변수로 정의되어 있는 Channel을 통해 자원으로의 접근이 가능하다. ## Coroutine builder 코틀린은 코루틴을 만들수 있는 도구를 지원하는 형태 - launch - runBlocking - async #### launch builder 현재 쓰레드를 블로킹하지 않고 새 코루틴을 시작하며, 이에 대한 참조를 Job 객체로 반환한다. 이 Job이 취소되면 코루틴이 취소 된다. CoroutineContext는 해당 CoroutineScope의 것을 상속하며, launch 함수의 매개변수로 추가적인 Context를 지정할 수도 있다. 해당 Context에 Dispatcher나 다른 ContinuationInterceptor가 없으면 Dispatchers.Default가 지정된다. 기본적으로 코루틴은 즉시 실행되도록 스케쥴링된다. 다른 시작 옵션은 start 매개변수를 통해 지정할 수 있으며, 선택적으로 CoroutineStart.Lazy로 설정하여 코루틴을 느리게 시작할 수 있다. 이 경우 코루틴 Job은 새로운 상태로 생성된다. start의 기본값은 CoroutineStart.DEFAULT 다. **이 코루틴의 catch 되지 않은 예외들은 기본적으로 해당 컨텍스트의 상위 작업을 취소한다.** 이는 launch가 다른 코루틴의 컨텍스트와 함께 사용 될 때 포착되지 않은 예외가 상위 코루틴의 취소로 이어진다는 것을 의미 한다. #### runBlocking builder 새 코루틴을 실행하고 완료될 때까지 현재 사용하고 있는 스레드의 인터럽트를 차단한다. 이 함수는 코루틴에서 사용하지 않는 것이 좋다. 이 함수는 단지 main 함수나 테스트에서 사용되기 위해 설계되었기 때문이다. runBlocking은 자식이 예외발생으로 인해 종료되면 그냥 종료된다. 이 코루틴 빌더의 기본적인 CoroutineDispatcher는 이 코루틴이 완료될 때까지 차단된 스레드에서 Continuation을 처리하는 내부 이벤트 루프의 구현이다. CoroutineDispatcher가 컨텍스트에서 명시적으로 지정되면 새 코루틴은 현재 스레드가 차단되는 동안 지정된 디스패처의 컨텍스트에서 실행된다. 이 차단된 스레드가 인터럽트 되면 코루틴 job이 취소되고 interruptedException이 발생한다. #### async builder 코루틴을 만들고 Deffered의 구현으로 해당 결과를 반환한다. >Deferred: 결과값을 수신하는 비동기 작업 >직역하면 연기 라는 뜻을 가진다. "결과값 수신을 연기한다"라는 뜻인데, >미래의 어느 시점에 결과값이 올 것을 뜻한다. 실행 중인 코루틴은 deferred의 결과가 취소되면 취소된다. 결과 코루틴은 다른 언어 및 프레임워크의 유사한 기초 요소와 비교하여 중요한 차이점이 있다. **구조화 된 동시성 패러다임을 시행하지 못하면 상위 작업을 취소한다.** 해당 동작을 변경하려면 SupervisorJob 또는 supervisorScope를 사용할 수 있다. 코루틴 컨텍스트는 CoroutineScope에서 상속되며 context 인자와 함께 추가적인 컨텍스트를 지정할 수 있다. 컨텍스트에 dispatcher나 다른 ContinuationInterceptor가 없으면 Dispatchers.Default가 사용된다. 부모 작업도 CoroutineScope에서 상속되지만 해당 컨텍스트 요소로 오버라이드 될 수 있다. launch와 마찬가지로 기본적으로 코루틴은 즉시 실행되도록 스케쥴링 된다 ### 스프링에서는? 의존성 추가로 코루틴 지원기능을 추가할 수 있음 https://docs.spring.io/spring-framework/docs/current/reference/html/languages.html#coroutines 대표적으로 컨트롤러에 사용 가능 > 예제 참고 https://docs.spring.io/spring-framework/docs/current/reference/html/languages.html#controllers 배민 스프링 웹플럭스 패턴 https://techblog.woowahan.com/7349/