본문으로 건너뛰기

🚀 왜 코루틴인가?

📖 코루틴이란?

**코루틴(Coroutines)**은 비동기 프로그래밍을 쉽고 직관적으로 만드는 Kotlin의 핵심 기능입니다. 복잡한 콜백 지옥에서 벗어나 마치 동기 코드처럼 작성할 수 있습니다!

💡 전통적인 비동기의 문제

콜백 지옥

// ❌ 콜백 방식 (읽기 어렵고 유지보수 힘듦)
fun fetchUser(userId: String, callback: (User?) -> Unit) {
fetchFromNetwork(userId) { result ->
if (result != null) {
fetchProfile(result.id) { profile ->
if (profile != null) {
fetchPosts(profile.id) { posts ->
callback(User(result, profile, posts))
}
} else {
callback(null)
}
}
} else {
callback(null)
}
}
}

쓰레드의 비용

// ❌ 쓰레드는 비싸다
fun downloadFiles() {
repeat(1000) {
Thread {
// 각 쓰레드는 메모리를 많이 사용
downloadFile("file_$it.txt")
}.start()
}
// 메모리 부족 가능성!
}

✨ 코루틴의 장점

1. 간결한 코드

// ✅ 코루틴 (읽기 쉽고 직관적)
suspend fun fetchUser(userId: String): User? {
val result = fetchFromNetwork(userId) ?: return null
val profile = fetchProfile(result.id) ?: return null
val posts = fetchPosts(profile.id)
return User(result, profile, posts)
}

2. 가벼운 동시성

// ✅ 수천 개의 코루틴도 문제없음
suspend fun downloadFiles() {
coroutineScope {
repeat(10000) {
launch {
// 가벼운 코루틴!
downloadFile("file_$it.txt")
}
}
}
}

3. 구조화된 동시성

// ✅ 자동으로 생명주기 관리
suspend fun processData() {
coroutineScope {
val job1 = launch { task1() }
val job2 = launch { task2() }

// 모든 작업이 끝나면 자동으로 완료
}
}

🎯 실제 사용 예시

네트워크 요청

// 콜백 없이 깔끔하게!
suspend fun loadUserData(userId: String): UserData {
val user = apiService.getUser(userId)
val posts = apiService.getPosts(userId)
val friends = apiService.getFriends(userId)

return UserData(user, posts, friends)
}

UI 업데이트

// 메인 스레드를 블록하지 않고
suspend fun updateUI() {
val data = withContext(Dispatchers.IO) {
// 백그라운드에서 데이터 로드
loadDataFromDatabase()
}

// 메인 스레드에서 UI 업데이트
updateViews(data)
}

병렬 처리

// 여러 작업을 동시에!
suspend fun fetchAllData(): CombinedData = coroutineScope {
val users = async { fetchUsers() }
val products = async { fetchProducts() }
val orders = async { fetchOrders() }

CombinedData(
users.await(),
products.await(),
orders.await()
)
}

🔥 코루틴 vs 쓰레드

특징코루틴쓰레드
무게매우 가벼움무거움
생성 비용거의 없음비쌈
개수수만 개 가능수천 개 한계
전환 비용낮음높음
취소쉬움어려움

성능 비교

// 코루틴: 100,000개 실행
suspend fun testCoroutines() {
val time = measureTimeMillis {
coroutineScope {
repeat(100_000) {
launch {
delay(1000)
}
}
}
}
println("코루틴: ${time}ms") // ~1000ms
}

// 쓰레드: 10,000개만 해도...
fun testThreads() {
val time = measureTimeMillis {
repeat(10_000) {
Thread {
Thread.sleep(1000)
}.start()
}
}
println("쓰레드: ${time}ms") // 메모리 부족 가능!
}

🎨 코루틴의 핵심 개념

Suspend (일시 중단)

// suspend 함수는 코루틴 안에서만 호출 가능
suspend fun doWork() {
delay(1000) // 쓰레드를 블록하지 않고 대기
println("작업 완료")
}

구조화된 동시성

// 부모-자식 관계로 생명주기 관리
fun main() = runBlocking {
launch { // 부모
launch { // 자식 1
delay(1000)
println("자식 1 완료")
}
launch { // 자식 2
delay(2000)
println("자식 2 완료")
}
println("부모는 자식이 끝날 때까지 대기")
}
}

🤔 자주 묻는 질문

Q1. 코루틴은 새로운 쓰레드인가요?

A: 아닙니다! 코루틴은 쓰레드 위에서 동작하는 경량 작업 단위입니다.

// 하나의 쓰레드에서 여러 코루틴 실행 가능
fun main() = runBlocking {
repeat(3) { i ->
launch {
println("코루틴 $i: ${Thread.currentThread().name}")
}
}
}
// 모두 같은 쓰레드에서 실행될 수 있음!

Q2. 모든 함수를 suspend로 만들어야 하나요?

A: 아닙니다! 비동기 작업이 필요한 함수만 suspend로 만듭니다.

// ❌ 필요 없음
suspend fun add(a: Int, b: Int) = a + b

// ✅ 필요함
suspend fun fetchData() = withContext(Dispatchers.IO) {
loadFromNetwork()
}

Q3. 언제 코루틴을 사용해야 하나요?

A: 이런 경우에 사용하세요!

// ✅ 네트워크 요청
suspend fun loadData() = api.fetchData()

// ✅ 데이터베이스 작업
suspend fun saveUser(user: User) = db.insert(user)

// ✅ 파일 I/O
suspend fun readFile(path: String) = withContext(Dispatchers.IO) {
File(path).readText()
}

// ✅ 긴 계산 작업
suspend fun heavyComputation() = withContext(Dispatchers.Default) {
// 복잡한 계산
}

🎬 마치며

코루틴으로 비동기 프로그래밍을 쉽게!

핵심 정리:
✅ 콜백 지옥 탈출
✅ 가벼운 동시성
✅ 직관적인 코드
✅ 구조화된 생명주기
✅ 쓰레드보다 훨씬 효율적

다음 단계: 코루틴 기초에서 실제로 코루틴을 사용해보세요!