ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Coroutine 등장 배경
    BackEnd/coroutine 2025. 5. 3. 22:00
    반응형

    Overview

      인프런- 코틀린 코루틴 완전 정복 강의를 수강하고 정리합니다.

     

    GitHub - HanseomKim/coroutinelecture: 『코틀린 코루틴 완전 정복』, 조세영, 인프런(2024) 저장소 입니다.

    『코틀린 코루틴 완전 정복』, 조세영, 인프런(2024) 저장소 입니다. Contribute to HanseomKim/coroutinelecture development by creating an account on GitHub.

    github.com

     

    단일 스레드 애플리케이션

      스레드 하나만 사용해 실행되는 애플리케이션입니다.

     

    단일 스레드 애플리케이션의 한계

      스레드는 한 번에 하나의 작업밖에 수행하지 못하기 때문에, 한 작업이 오래 걸리는 경우 문제가 됩니다. 메인 스레드 또한 예외가 아닙니다.

     

    멀티 스레드 프로그래밍

      여러 개의 스레드를 사용해 작업을 처리하는 프로그래밍 기법입니다.

     

    Thread를 사용한 멀티 스레드 프로그래밍

    class ExampleThread : Thread() {
        override fun run() {
            println("[${Thread.currentThread().name}] 시작")
            Thread.sleep(2000L)
            println("[${Thread.currentThread().name}] 종료")
        }
    }
    
    fun main() {
        println("[${Thread.currentThread().name}] 시작")
        ExampleThread().start() // 새로운 Thread를 시작합니다.
        Thread.sleep(1000L)
        println("[${Thread.currentThread().name}] 종료")
    }

     

      Kotlin에서는 thread 함수를 제공하기 때문에 다음과 같이 작성할 수 있습니다.

    import kotlin.concurrent.thread
    
    fun main() {
        println("[${Thread.currentThread().name}] 시작")
        thread {
            println("[${Thread.currentThread().name}] 시작")
            Thread.sleep(2000L)
            println("[${Thread.currentThread().name}] 종료")
        }
        Thread.sleep(1000L)
        println("[${Thread.currentThread().name}] 종료")
    }

     

      Thread 클래스를 사용하면 간단하게 병렬 처리를 수행할 수 있다는 장점을 가지지만, 다음과 같은 한계가 존재합니다.

    1. Thread의 start 함수를 호출할 때마다 새로운 스레드가 생성되고 재사용이 어렵습니다. 스레드는 비싼 자원이기에 재사용이 어려운 것은 치명적입니다.
    2. 개발자가 스레드 생성과 관리에 대한 책임을 가집니다. 즉, 개발자의 실수나 오류로 인해 메모리 누수가 일어날 수 있습니다. 또한, 프로그램이 복잡해질 수록 스레드의 생성과 관리를 직접 하는 것은 불가능에 가까워집니다.

     

    Executor 프레임웍

      위와 같은 문제를 해결하기 위해 Java 1.5에서 Executor 프레임웍이 등장하였습니다. Executor 프레임웍이란 스레드의 집합인 스레드 풀을 미리 생성해놓고, 작업을 요청 받으면 쉬고 있는 스레드에 작업을 분배할 수 있는 시스템입니다.

    • 개발자가 더이상 스레드를 직접 관리하지 않도록 했습니다. 개발자는 스레드 개수 지정과 작업만 제출하면 됩니다.
    • 스레드의 재사용을 손쉽게 가능하게 만들었습니다.
    import java.util.concurrent.Executors
    
    fun main() {
        // ExecutorService 생성
        val executorService = Executors.newFixedThreadPool(2)
    
        // 작업1 제출
        executorService.submit {
            println("[${Thread.currentThread().name}] 작업1 시작")
            Thread.sleep(1000L) // 1초간 대기
            println("[${Thread.currentThread().name}] 작업1 완료")
        }
    
        // 작업2 제출
        executorService.submit {
            println("[${Thread.currentThread().name}] 작업2 시작")
            Thread.sleep(1000L) // 1초간 대기
            println("[${Thread.currentThread().name}] 작업2 완료")
        }
    
        // 작업3 제출
        executorService.submit {
            println("[${Thread.currentThread().name}] 작업3 시작")
            Thread.sleep(1000L) // 1초간 대기
            println("[${Thread.currentThread().name}] 작업3 완료")
        }
    
        // ExecutorService 종료
        executorService.shutdown()
    }

     

    Executor 프레임웍의 한계

      스레드 블로킹이 일어난다. 스레드 블로킹이란 스레드가 사용될 수 없는 상태에 있는 것을 뜻합니다.

    import java.util.concurrent.Executors
    import java.util.concurrent.Future
    
    fun main() {
        val executorService = Executors.newFixedThreadPool(2)
    
        // ExecutorService에 반환 값이 있는 작업 제출
        val future: Future<String> = executorService.submit<String> {
            Thread.sleep(2000)
            return@submit "더미 결과값"
        }
    
        // 반환값이 올때까지 메인 스레드 블로킹
        val result = future.get()
        println(result)
    
        executorService.shutdown()
    }

     

    CompletableFuture

      Java 1.8부터는 CompletableFuture을 사용해 스레드 블로킹의 문제를 해결하였습니다. 다만, 콜백 지옥이 생기고 예외 처리가 어렵다는 한계가 있습니다.

    import java.util.concurrent.CompletableFuture
    import java.util.concurrent.Executors
    
    fun main() {
        val executorService = Executors.newFixedThreadPool(2)
    
        // ExecutorService에 반환 값이 있는 작업 제출
        val completableFuture = CompletableFuture.supplyAsync(
            {
                Thread.sleep(2000L)
                return@supplyAsync "더미 결과값"
            },
            executorService
        )
    
        // 콜백 형식으로 결과값 처리
        completableFuture.thenAccept { result ->
            println("결과:${result}")
        }
    
        executorService.shutdown()
    }

     

    RxJava

      결과값을 데이터 스트림으로 처리하였으나, 여전히 스레드 블로킹이 발생합니다.

     

    스레드 기반 작업의 문제

    • 스레드 기반 작업들은 작업의 전환이 어렵고, 전환에 드는 비용이 비쌉니다.
    • 간단한 작업에서는 앞서 본 콜백 방식을 사용하거나, 체이닝 함수를 사용하는 방식으로 스레드 블로킹 문제를 해결할 수 있지만, 실제 애플리케이션은 작업 간의 종속성이 복잡해 스레드 블로킹이 발생하는 것은 필연적입니다.

     

    Coroutine

      코루틴에서는 '코루틴'이라 불리는 작업 단위를 사용합니다.

    • 코루틴은 스레드의 사용 권한을 양보할 수 있습니다.
    • 즉, 스레드에 붙였다 뗐다 할 수 있는 작업 단위입니다.
    • 코루틴은 경량 스레드입니다.

    반응형

    'BackEnd > coroutine' 카테고리의 다른 글

    Coroutine 순차 처리  (0) 2025.05.09
    CoroutineDispatcher  (0) 2025.05.06
    runBlocking  (0) 2025.05.05

    댓글

Designed by Tistory.