백고등어 개발 블로그
코틀린 강의 9강: 코루틴 기초 본문
728x90
반응형
9강: 코루틴 기초
코루틴이란?
코루틴(Coroutine)은 비동기 프로그래밍을 쉽게 작성할 수 있게 해주는 코틀린의 핵심 기능입니다. 경량 스레드라고 불리며, 수천 개의 코루틴을 동시에 실행해도 성능 저하가 거의 없습니다.
코루틴 시작하기
의존성 추가
코루틴을 사용하려면 먼저 의존성을 추가해야 합니다. Gradle의 경우:
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}
첫 번째 코루틴
import kotlinx.coroutines.*
fun main() = runBlocking { // 메인 스레드를 차단하는 코루틴 빌더
launch { // 새로운 코루틴 시작
delay(1000L) // 1초 대기 (논블로킹)
println("World!")
}
println("Hello,")
}
// 출력:
// Hello,
// (1초 후)
// World!
코루틴 빌더
runBlocking
현재 스레드를 차단하고 코루틴이 완료될 때까지 기다립니다. 주로 main 함수나 테스트에서 사용합니다:
fun main() = runBlocking {
println("Start")
delay(1000L)
println("End")
}
launch
새로운 코루틴을 시작하고 Job을 반환합니다. 결과값을 반환하지 않습니다:
fun main() = runBlocking {
val job = launch {
delay(1000L)
println("코루틴 완료")
}
println("메인 코루틴")
job.join() // 코루틴이 완료될 때까지 대기
}
async
결과값을 반환하는 코루틴을 시작합니다. Deferred<T>를 반환합니다:
fun main() = runBlocking {
val deferred = async {
delay(1000L)
"결과값"
}
println("계산 중...")
val result = deferred.await() // 결과를 기다림
println("결과: $result")
}
일시 중단 함수(Suspend Functions)
suspend 키워드로 표시된 함수는 코루틴 내에서만 호출할 수 있습니다:
suspend fun fetchData(): String {
delay(2000L) // 2초 대기
return "데이터"
}
fun main() = runBlocking {
println("데이터 가져오는 중...")
val data = fetchData()
println("받은 데이터: $data")
}
여러 일시 중단 함수 호출
suspend fun fetchUser(): String {
delay(1000L)
return "사용자 정보"
}
suspend fun fetchPosts(): String {
delay(1500L)
return "게시글 목록"
}
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
// 순차 실행 (약 2.5초 소요)
val user = fetchUser()
val posts = fetchPosts()
val endTime = System.currentTimeMillis()
println("$user, $posts")
println("소요 시간: ${endTime - startTime}ms")
}
동시 실행
async를 사용한 병렬 실행
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
// 동시 실행 (약 1.5초 소요)
val userDeferred = async { fetchUser() }
val postsDeferred = async { fetchPosts() }
val user = userDeferred.await()
val posts = postsDeferred.await()
val endTime = System.currentTimeMillis()
println("$user, $posts")
println("소요 시간: ${endTime - startTime}ms")
}
구조화된 동시성(Structured Concurrency)
코루틴은 구조화된 동시성을 따릅니다. 부모 코루틴이 취소되면 자식 코루틴도 모두 취소됩니다:
fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
println("작업 $i")
delay(500L)
}
}
delay(1300L)
println("취소합니다")
job.cancel() // 코루틴 취소
job.join() // 취소 완료 대기
println("완료")
}
코루틴 스코프(Coroutine Scope)
GlobalScope (권장하지 않음)
fun main() = runBlocking {
GlobalScope.launch {
delay(1000L)
println("GlobalScope")
}
println("메인")
delay(2000L) // GlobalScope가 완료될 때까지 대기
}
CoroutineScope
권장되는 방식은 CoroutineScope를 명시적으로 만들어 사용하는 것입니다:
fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.Default)
scope.launch {
delay(1000L)
println("코루틴 1")
}
scope.launch {
delay(500L)
println("코루틴 2")
}
delay(2000L) // 코루틴들이 완료될 때까지 대기
}
코루틴 컨텍스트와 디스패처
Dispatchers
코루틴이 실행될 스레드를 지정합니다:
fun main() = runBlocking {
// Dispatchers.Default: CPU 집약적 작업
launch(Dispatchers.Default) {
println("Default: ${Thread.currentThread().name}")
}
// Dispatchers.IO: 네트워크, 파일 I/O 작업
launch(Dispatchers.IO) {
println("IO: ${Thread.currentThread().name}")
}
// Dispatchers.Main: UI 스레드 (Android에서만)
// launch(Dispatchers.Main) { ... }
// Dispatchers.Unconfined: 제한 없음 (일반적으로 사용하지 않음)
launch(Dispatchers.Unconfined) {
println("Unconfined: ${Thread.currentThread().name}")
}
delay(1000L)
}
withContext를 사용한 컨텍스트 전환
suspend fun fetchDataFromNetwork(): String {
return withContext(Dispatchers.IO) {
// I/O 작업
delay(1000L)
"네트워크 데이터"
}
}
fun main() = runBlocking {
println("메인 스레드: ${Thread.currentThread().name}")
val data = fetchDataFromNetwork()
println("데이터: $data")
println("다시 메인 스레드: ${Thread.currentThread().name}")
}
예외 처리
try-catch
fun main() = runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("작업 $i")
delay(500L)
}
} catch (e: CancellationException) {
println("취소됨")
throw e // CancellationException은 다시 던져야 함
} finally {
println("정리 작업")
}
}
delay(1300L)
job.cancel()
job.join()
}
CoroutineExceptionHandler
fun main() = runBlocking {
val handler = CoroutineExceptionHandler { _, exception ->
println("예외 처리: ${exception.message}")
}
val job = GlobalScope.launch(handler) {
throw RuntimeException("오류 발생!")
}
job.join()
}
타임아웃
withTimeout
fun main() = runBlocking {
try {
withTimeout(1300L) {
repeat(1000) { i ->
println("작업 $i")
delay(500L)
}
}
} catch (e: TimeoutCancellationException) {
println("타임아웃!")
}
}
withTimeoutOrNull
fun main() = runBlocking {
val result = withTimeoutOrNull(1300L) {
repeat(1000) { i ->
println("작업 $i")
delay(500L)
}
"완료"
}
println("결과: $result") // 결과: null
}
실전 예제
여러 API 호출 병렬 처리
data class User(val name: String)
data class Posts(val count: Int)
data class Comments(val count: Int)
suspend fun fetchUser(userId: Int): User {
delay(1000L)
return User("사용자-$userId")
}
suspend fun fetchPosts(userId: Int): Posts {
delay(1500L)
return Posts(10)
}
suspend fun fetchComments(userId: Int): Comments {
delay(800L)
return Comments(25)
}
data class UserProfile(val user: User, val posts: Posts, val comments: Comments)
suspend fun fetchUserProfile(userId: Int): UserProfile = coroutineScope {
val userDeferred = async { fetchUser(userId) }
val postsDeferred = async { fetchPosts(userId) }
val commentsDeferred = async { fetchComments(userId) }
UserProfile(
user = userDeferred.await(),
posts = postsDeferred.await(),
comments = commentsDeferred.await()
)
}
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val profile = fetchUserProfile(1)
val endTime = System.currentTimeMillis()
println(profile)
println("소요 시간: ${endTime - startTime}ms") // 약 1500ms
}
진행 상황 업데이트
suspend fun downloadFile(name: String, progress: (Int) -> Unit) {
repeat(10) { i ->
delay(300L)
progress((i + 1) * 10)
}
println("$name 다운로드 완료")
}
fun main() = runBlocking {
launch {
downloadFile("파일1.zip") { percent ->
println("파일1: $percent%")
}
}
launch {
downloadFile("파일2.zip") { percent ->
println("파일2: $percent%")
}
}
delay(5000L)
}
코루틴 사용 시 주의사항
- GlobalScope 지양: 생명주기 관리가 어려우므로 명시적인 CoroutineScope 사용을 권장합니다.
- 적절한 Dispatcher 선택: CPU 집약적 작업은 Default, I/O 작업은 IO를 사용합니다.
- 예외 처리: 코루틴의 예외는 부모로 전파되므로 적절히 처리해야 합니다.
- 취소 가능하게 작성: isActive를 확인하거나 ensureActive()를 호출합니다.
마치며
이번 강의에서는 코틀린 코루틴의 기초를 알아보았습니다. 코루틴은 비동기 프로그래밍을 동기 코드처럼 작성할 수 있게 해주는 강력한 도구입니다. launch와 async의 차이, 적절한 Dispatcher 선택, 구조화된 동시성 개념을 잘 이해하면 효율적인 비동기 코드를 작성할 수 있습니다. 다음 강의에서는 코틀린의 고급 기능들을 알아보겠습니다.
728x90
반응형
'코틀린 강의' 카테고리의 다른 글
| 코틀린 강의 10강: 실전 프로젝트와 모범 사례 (0) | 2025.10.28 |
|---|---|
| 코틀린 강의 8강: 제네릭 (0) | 2025.10.28 |
| 코틀린 강의 7강: 예외 처리 (0) | 2025.10.28 |
| 코틀린 강의 6강: 컬렉션 (0) | 2025.10.28 |
| 코틀린 강의 5강: 클래스와 객체 (0) | 2025.10.28 |