Kotlin lang

[kotlin] 코틀린 코루틴에 대한 개념 정리 (4)

Razelo 2024. 4. 25. 22:55

코틀린에는 구조화된 동시성이란 개념이 있다고 한다. 

 

꽤나 비중있는 주제인것 같아 깔끔하게 정리해보고자 한다. 

 

코루틴에는 코루틴 내부에서 또 코루틴을 만들 수 있다.

이 경우 부모코루틴과 자식 코루틴이 생긴다. 코루틴에는 Job객체로 코루틴을 추적할 수 있는데, Job객체 내부에는 parent와 children이라는 프로퍼티가 있어서 이걸로 부모 자식간 양방향 참조가 가능하다.

참고로 parent는 Job?타입이고, children은 Sequence<Job> 타입이다. parent가 Job?인 이유는 루트 코루틴의 경우 부모가 없을 수도 있기 때문이다. 

 

그리고 CoroutineContext의 경우 부모에서 자식으로 상속되고, 자식에서 별도의 CoroutineContext를 사용한다면 상속받은걸 덮어씌우게 된다. 

이때 Job의 경우는 부모 자식 상관없이 각 코루틴마다 고유하게 갖게 된다. 대신 위에서 말한 것처럼 부모 자식간 참조관계만 가지고 있는 것이다. 

 

재밌는건, 부모로 취소 요청이 갈 경우 자식에게도 취소 요청이 전파된다는 것이다. 그래야만 불필요한 리소스 낭비를 막을 수 있다는 것이다. 

 

그리고 또 자식 코루틴이 모두 완료되어야 부모 코루틴도 완료될 수 있다고 한다. 

 

참고로 코루틴의 구조화는 큰 작업을 작은 작업으로 나누는게 기본이다. 왜냐면 작은 작업들이 완료되어야 큰 작업이 완료될 수 있기 때문이다. 

 

코루틴은 Job객체를 사용해서 코루틴을 구조화한다. 이 구조화를 유지하는게 코루틴의 안전성에 핵심이라서 한다. 그래서 왠한하면 코루틴 구조화를 깨는 코드를 작성하지 않는게 좋다고 한다.! 

 

참고로 구조화를 깨야하는 케이스도 몇가지 존재한다고 한다. 

사용자가 원할때 언제든 종료해야하는 작업이거나, 어플리케이션 전체 생명주기에 걸친 긴 작업이거나 독립적인 작업인 경우에 그러하다고 한다. 물론 누수안생기게 잘 고려해야한다. 

 

우리가 launch, async로 얻어내는 Job객체의 경우 (물론 async인 경우 Deferred<T>를 얻지만 이것도 Job의 타입 중 하나이므로 동일하게 이야기한다.) 하위 코루틴들이 종료되었을 경우 해당 부모 코루틴이 종료될 수 있지만, Job()으로 직접 만들어낸 별도의 루트Job의 경우 complete()를 명시적으로 사용해줘야만 자식 코루틴이 종료되었을때 부모가 안전하게 종료할 수 있다고 한다. 아니면 계속 돌아간다고 한다. 

 

runBlocking과 launch의 차이점에 대해 말해보자면...

runBlocking이 참 흥미로운데, 호출 스레드를 블로킹해버린다고 한다. 그런데 launch는 블로킹하지 않기 때문에 다른 코루틴이 작업을 해당 호출 스레드에서 진행할 수 있다. 그래서 runBlocking을 특수한 경우에 사용한다고 한다. 블로킹 방식의 특정 코드와 비동기 코드 간 브릿지 역할을 하거나, 써드 파티 라이브러리를 붙였을때 이걸 써야할 필요가 생길 수 있다고 한다. 물론 특수한 케이스므로 일반적인 코루틴 내에서 이걸 쓰는걸 권장되지 않는다고한다. 성능이 급격히 저하될 수 있다고 한다... 물론 스레드에 다른 코루틴이 못들어가게 막아버리니 코루틴 장점을 지우는거나 다름없다. 

 

두서없게 정리해놓았는데, 일단은 기록해둔다. 

반응형