본문 바로가기
프로그래밍/Kotlin

[Kotlin] Async vs Launch (Deffered vs Job)

by Youngs_ 2023. 1. 12.

선요약 : Deffered는 return값이 있어서 변수에 바로 값을 넣을수있다.
Job은 return값이 없어서 변수에 값을 넣으려면 Job 안에서 변수에 값을 넣어야한다.

Deffered = async의 반환값, 즉 async를 사용할때
Job = launch의 반환값, 즉 launch를 사용할때

Async와 Launch는 반환값 이외의 차이점은 없다.

Coroutines VS Async

async/await

kotlinx.coroutines.CoroutineScope.async

 

- async : 코드 블락을 정의

- await : 코드 블락의 종료를 대기

- async(/* Thread type 지정 */) : launch와 동일하며, 지정하지 않으면 상위 scope thread type을 따른다.

- 1개 이상의 coroutine 동기화가 필요한 경우 유용하게 사용할 수 있다.

 

async는 사실상 launch와 같은 일을 한다.

유일한 차이는 launch가 Job을 반환하는 반면 async는 Deffered를 반환한다는 점뿐이다.

Deffered는 Job을 상속한 클래스이기 때문에 launch 대신 async를 사용해도 항상 아무 문제가 없다.

실제로 두 함수의 구현을 보면 다음 코드와 같다.

Deffered vs Job

- Job은 아무 타입 파라미터가 없는데 Deffered 타입 파라미터가 있는 제네릭 타입

- Deffered 안에는 await() 함수가 정의되어 있다는 점

 

 

둘의 정의는 아래와 같이 요약하여 확인할 수 있다.

public interface Deferred<out T> : Job { ... }
public interface Job : CoroutineContext.Element { ... }

 

Defferred의 타입 파라미터는 코루틴이 계산을 하고 돌려주는 값의 타입이다.

Job은 Unit을 돌려주는 Defferred<Unit>이라고 생각할 수도 있을 것이다.

따라서 async는 코드 블록을 비동기로 실행할 수 있고,

async가 반환하는 Deffered의 await을 사용해서 코루틴이 결과 값을 내놓을 때까지 기다렸다가 결과 값을 얻어낼 수 있다.

제공하는 코루틴 컨텍스트에 따라 여러 스레드를 사용하거나 한 스레드 안에서 제어만 왔다 갔다 할 수도 있다

 

다음은 1부터 3까지 수를 더하는 과정을 async/await을 사용해 처리하는 모습을 보여준다.

 

fun sumAll() {
    runBlocking {
        val d1 = async { delay(1000L); 1 }
        log("after async(dl)")
        val d2 = async { delay(2000L); 2 }
        log(" after async(d2)")
        val d3 = async { delay(3000L); 3 }
        log("after async(d3)")

        log("1+2+3 = ${dl.await() + d2.await() + d3.await()}")
        log ("after await all & add")
    }
} // 5sec 136ms

fun now() = ZonedDateTime.now().toLocalTime().truncatedTo(ChronoUnit.SECONDS)
fun log(msg: String) = println("${now()}:${Thread.currentThread()}:${msg}")

 

22:45:25.208:Thread[main @coroutine#1,5,main]:after async(d1)
22:45:25.232:Thread[main @coroutine#1,5,main]: after async(d2)
22:45:25.232:Thread[main @coroutine#1,5,main]:after async(d3)
22:45:28.247:Thread[main @coroutine#1,5,main]:1+2+3 = 6
22:45:28.247:Thread[main @coroutine#1,5,main]:after await all & add

 

잘 살펴보면 d1, d2 ,d3를 하나하나 순서대로 실행하면 총 6초(6000밀리초) 이상이 걸리지만,

병렬 처리에서 이런 경우를 직렬화해 실행한다고 말한다

실제로 총 3초가 걸렸음을 알 수 있다. 

 

또한 async로 코드를 실행하는 데는 시간이 거의 걸리지 않았다. 

이 예제에서는 겨우 3개의 비동기 코드만을 실행했지만,

비동기 코드가 늘어날 수록 async/await을 사용한 비동기의 성능은 굉장히 뛰어날 것이다.

 

스레드를 여럿 사용하는 병렬 처리와 달리, 모든 async 함수들이 메인 스레드 안에서 실행됨을 볼 수 있다. 

이 부분이 async/await과 스레드를 사용한 병렬 처리의 큰 차이이다.

 

실행하려는 작업이 시간이 얼마 걸리지 않거나 I/O에 의한 대기 시간이 크고,

CPU 코어 수가 작아 동시에 실행할 수 있는 스레드 개수가 한정된 경우에는

특히 코루틴과 일반 스레드를 사용한 비동기 처리 사이에 차이가 커진다.

 

 

참고로 launch를 사용한 코드는 아래와 같고, async와 동일한 효과를 볼 수 있다.

 

fun sumAllLaunch() = run {
    var d1 = 0
    var d2 = 0
    var d3 = 0

    runBlocking {
        launch { delay(1000L); d1 = 1 }
        log("after launch(dl)")
        launch { delay(2000L); d2 = 2 }
        log("after launch(d2)")
        launch { delay(3000L); d3 = 3 }
        log("after launch(d3)")
    }

    log("1+2+3 = ${d1 + d2 + d3}")
    log("after await all & add")
}

 

22:45:28.263:Thread[main @coroutine#5,5,main]:after launch(d1)
22:45:28.264:Thread[main @coroutine#5,5,main]:after launch(d2)
22:45:28.265:Thread[main @coroutine#5,5,main]:after launch(d3)
22:45:31.267:Thread[main,5,main]:1+2+3 = 6
22:45:31.267:Thread[main,5,main]:after await all & add

 


출처 : https://gngsn.tistory.com/208

 

Kotlin, 코루틴 제대로 이해하기 - (2)

kotlin의 Coroutine을 이해하는 것이 해당 포스팅의 목표입니다. 🔗 Kotlin 시리즈 모아보기 사실, 순서대로라면 Class에 대한 내용을 다뤄야하는데, 추석이 끝나고 마음이 급해져서 코루틴이라도 파보

gngsn.tistory.com

 

댓글