Kotlin lang

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

Razelo 2024. 4. 20. 10:40

최근 코틀린을 다룰 일이 있었다. 

 

코드는 그냥 작성하면 되는데, 코루틴에 대한 개념이 좀 부족했다고 생각했다. 애매하게 알고 있는 상태에서 코루틴의 기능을 쓰는게 썩 기분이 좋진 않았다. 뭔가 찝찝했다. 그래서 이번 기회에 코루틴에 대해 시간을 갖고 천천히 알아보고자 한다. 

 


 

코루틴은 기본적으로 경량 스레드라고 불린다. 

왜 경량 스레드냐면, 스레드에 붙였다 뗄 수 있기 때문이다. 

 

왜 코루틴이 필요하냐를 정리하자면,

멀티스레드 프로그래밍에서의 일부 한계점을 극복하고자함이다.

 

우선 싱글 스레드부터 살펴보자. 스레드가 하나이므로 단 하나의 작업만 할 수 있다. 모든 일은 순차적으로 진행된다. cpu바운드던, 네트워크 IO 작업이던 모든지 하나라서 그다지 효율적이지 않다.

 

그래서 등장한게 멀티스레드 프로그래밍이다. 스레드가 여러개다.

메인 스레드 이외에 다른 곳에서 별도로 작업을 실행시킬 수 있다. 그리고 직접 개발자가 스레드를 만드는 과정 등이 번거로우므로 스레드 풀이라는 걸 만들어서 사용하기도 한다. 하지만 스레드 유휴타임이라는 문제가 있다. 스레드가 아무 일도 하지 않고, 다른 무언가를 기다리면서 자원을 낭비하는 것이다. 

 

그렇다면 해당 유휴타임에 다른 작업을 할 수 있도록 다른 작업을 그 타임에 끼워넣을 수 있진 않을까? 그래서작업 단위로 쪼갠 코루틴이 등장한다.

현재 작업을 잠시 일시중단시키고 나서 해당 스레드가 놀게 두지 않고 다른 코루틴 작업을 가져와서 유휴타임 동안 해당 스레드에서 실행시킨다. 이후 일시중지 시킨 곳으로부터 원하는 응답 혹은 처리 완료를 확인받으면 기존 작업을 재개한다. (일시중지시킬만한 예시 작업은 DB혹은 Api호출 같은 외부 작업이 있겠다)

 

코루틴은 주로 CoroutineDispatcher 객체를 통해 스레드풀을 관리한다.

CoroutineDispatcher란 코루틴 큐가 존재하고, 스레드풀에서 할당받은 스레드에 코루틴을 제출하여 실행시키는 대분류적인 개념적 기능이라고 보면 된다. 

 

재밌는건 여기서도 Dispatchers의 종류가 나뉜다.

Dispatchers.Default - 기본적인 CPU 바운드 작업 시 사용 

Dispatchers.IO - Network IO같은 작업 시 사용 

Dispatchers.Main - UI 렌더 시 메인에서 사용 (예외적인 기능 )

이 세가지는 코루틴 라이브러리가 관리하는 스레드 풀에서 각기 다른 고유의 풀을 가져간다. 

 

참고로 스레드 풀이란 여러 스레드를 미리 만들어두고 거기서 필요한 스레드 개수만큼 가져다가 꺼내서 사용하는 미리 만들어진 스레드 자판기 같은 개념이다. 스레드를 먹고 싶을때 음료 하나를 즉석에서 에이드로 만들어서 먹는게 아니라 자판기에 미리 잔뜩 만들어놓고 동전넣고 뽑아먹는 개념이다

 

관련해서 limitedParallelism이라는 함수도 있는데, 사용할 스레드를 제한하는 기능이다. (Dispatchers.Default랑 Dispatchers.IO에서 해당 함수의 동작이 각기 다르므로 사용법에 주의해야한다. Dispatchers.Defualt의 경우는 Default가 먹은 스레드풀 내에서의 스레드 개수를 제한하지만, Dispatchers.IO의 경우는 공유 스레드풀 내의 다른 곳에 별도의 스레드풀을 가져간다고 한다.)

 

Main은 주로 UI쪽에서 사용하는 개념이고, 별도의 라이브러리를 또 설치해야한다고 한다. 안드로이드 쪽에서 사용하는 개념인 것 같아서 자세히 보진 않았다. 

 

코루틴도 순서가 필요할 수 있다. A 코루틴과 B 코루틴이 동시 실행되어선 안되고, 반드시 A -> B 순서로 실행되어야할 수도 있다. (api 호출(B) 전 토큰을 만들어내는 작업(A)이 필요할 수도 있겠다)

 

이런 경우 join() 혹은 joinAll()을 사용하면 된다. 

val someJob: Job = launch(Dispatcher.IO) {

    // some job to do 

}

 

이 경우 리턴받은 job은 코루틴에 대한 작업 정보를 추적할 수 있게해주는 Job 객체이다. 

 

이 job에 대해 someJob.join()을 사용하던가 joinAll(someJob...)을 사용할 수도 있다. 

 

이 경우 스레드는 해당 시점에 join을 호출한 스레드 자체가 블로킹되어서 즉 join을 걸은 대상 코루틴들이 모두 완료될때까지 기다리고 완료되고 나서야 이후 실행가능하다. 

 

2편에서는 좀 더 흥미로운 내용을 더 알아보자. 


오늘은 이정도에서 마무리하고자 한다. 코루틴은 참 흥미로운 개념인 것 같다. 스레드 유휴 타임에 다른 작업을 할 수 있게끔 작업 단위로써 '코루틴' 이란 개념을 만들어서 해당 타임에 끼워넣을 수 있게 해주다니 당연하면서도 또 이걸 내부 구현하기 위해 어떻게 만들었지는 궁금하면서도 대단하다고 생각된다. 

 

업무에 도움이 많이 될 것 같으니 이것저것 찾아보자. 

 

반응형