# **코틀린의 코루틴과 비동기 프로그래밍**  

코틀린은 **코루틴(Coroutines)** 을 통해 **비동기(Asynchronous) 프로그래밍**을 쉽고 직관적으로 구현할 수 있다.  
코루틴은 **스레드보다 가볍고 효율적**이며, **콜백 지옥을 피할 수 있는 강력한 기능**을 제공한다.  

이 글에서는 **코루틴의 개념과 주요 기능**을 설명하고,  
**비동기 프로그래밍을 구현하는 예제**를 함께 살펴보겠다. 🚀  

---

## **1. 비동기 프로그래밍이란?**  
비동기 프로그래밍은 **작업이 완료될 때까지 기다리지 않고 다음 코드를 실행하는 방식**이다.  
즉, **시간이 오래 걸리는 작업(예: 네트워크 요청, 파일 I/O 등)** 도 프로그램이 멈추지 않고 실행된다.  

### **1.1. 전통적인 방식 (콜백 지옥)**
자바에서는 비동기 작업을 콜백(callback) 방식으로 처리해야 한다.  
콜백이 중첩되면 **콜백 지옥(Callback Hell)** 이 발생할 수 있다.  

#### **자바의 콜백 예제**
```java
void fetchData(Callback callback) {
    new Thread(() -> {
        try {
            Thread.sleep(2000); // 2초 대기 (네트워크 요청 가정)
            callback.onSuccess("데이터를 가져왔습니다!");
        } catch (InterruptedException e) {
            callback.onError(e);
        }
    }).start();
}

interface Callback {
    void onSuccess(String data);
    void onError(Exception e);
}

public static void main(String[] args) {
    fetchData(new Callback() {
        @Override
        public void onSuccess(String data) {
            System.out.println(data);
        }

        @Override
        public void onError(Exception e) {
            System.out.println("에러 발생: " + e.getMessage());
        }
    });
}
```
> - `fetchData()` 가 비동기적으로 데이터를 가져오지만, **콜백을 계속 중첩해서 작성해야 한다.**  
> - 코드가 복잡해지고 가독성이 나빠지는 문제가 있다.  

---

## **2. 코루틴이란?**
**코루틴(Coroutines)** 은 **비동기 프로그래밍을 간결하고 가독성 높게** 구현할 수 있도록 도와준다.  
코루틴을 사용하면 **콜백 없이도 순차적인 코드 스타일**로 **비동기 작업**을 작성할 수 있다.  

> **코루틴의 특징:**  
> - **스레드보다 가벼움** (필요할 때만 실행되고, 자동으로 일시 중단됨)  
> - **순차적 스타일로 작성 가능** (콜백 없이 비동기 코드 작성 가능)  
> - **비동기 코드가 직관적이고 읽기 쉬움**  

---

## **3. 코틀린에서 코루틴 사용하기**
### **3.1. 기본적인 코루틴 사용**
```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {  // 코루틴 블록 시작
    launch {  
        delay(1000L)  // 1초 대기
        println("코루틴 실행!")
    }
    println("메인 함수 실행")
}
```
**출력 결과:**  
```
메인 함수 실행
코루틴 실행!
```

> - `runBlocking {}`: 메인 함수에서 코루틴을 실행하는 블록  
> - `launch {}`: 새로운 코루틴을 실행  
> - `delay(1000L)`: 1초 동안 비동기 대기 (스레드 차단 없이 실행 가능)  

---

## **4. 코루틴 빌더 (`launch` vs `async`)**  
코루틴을 실행할 때는 **`launch`** 와 **`async`** 를 사용할 수 있다.  

| 빌더 | 반환값 | 특징 |
|------|------|------|
| `launch` | 없음 (`Job` 반환) | 단순히 코루틴을 실행 |
| `async` | `Deferred<T>` 반환 | 결과 값을 반환하는 비동기 작업 |

### **4.1. `launch` 사용 예시 (결과 값 없음)**
```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L)
        println("launch: 비동기 작업 완료!")
    }
}
```
> - `launch {}` 는 단순히 비동기 작업을 실행하고, 결과를 반환하지 않음.  
> - `Job` 객체를 반환하지만, 보통 `join()`을 호출하지 않는 이상 결과를 기다리지 않음.  

---

### **4.2. `async` 사용 예시 (결과 값 반환)**
```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val result = async {
        delay(1000L)
        "async: 비동기 작업 완료!"
    }
    println(result.await())  // 결과 값을 기다림
}
```
> - `async {}` 는 **결과 값을 반환하는 비동기 작업**을 실행.  
> - `await()` 을 호출해야 실제 값을 가져올 수 있음.  

---

## **5. 여러 개의 비동기 작업 실행 (`async` 활용)**  
비동기 작업을 병렬로 실행하려면 **`async {}` 를 여러 개 실행**하면 된다.  
```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    val time = measureTimeMillis {
        val job1 = async { fetchData(1) }
        val job2 = async { fetchData(2) }
        println(job1.await())  
        println(job2.await())  
    }
    println("총 실행 시간: $time ms")
}

suspend fun fetchData(id: Int): String {
    delay(1000L)  // 네트워크 요청 시뮬레이션
    return "데이터 $id 가져옴"
}
```
**출력 결과:**  
```
데이터 1 가져옴
데이터 2 가져옴
총 실행 시간: 1003 ms
```
> - `async {}` 로 실행된 두 개의 코루틴이 **병렬로 실행됨**.  
> - `await()` 를 호출하면 해당 코루틴이 완료될 때까지 기다림.  
> - 실행 시간은 1초 내외 (동시 실행되었기 때문).  

---

## **6. `suspend` 함수 사용하기**
코루틴에서 비동기 함수를 만들려면 **`suspend` 키워드**를 사용해야 한다.  
```kotlin
suspend fun fetchData(): String {
    delay(1000L)  // 1초 대기
    return "데이터 가져옴"
}

fun main() = runBlocking {
    val data = fetchData()
    println(data)
}
```
> - `suspend` 함수는 **코루틴 내에서만 실행 가능**  
> - `delay()` 같은 비동기 함수 호출 가능  

---

## **7. 예외 처리 (`try-catch` 활용)**
코루틴에서도 **예외 처리를 `try-catch` 로 할 수 있음**.  
```kotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        val result = async { errorTask() }.await()
        println(result)
    } catch (e: Exception) {
        println("예외 발생: ${e.message}")
    }
}

suspend fun errorTask(): String {
    delay(500L)
    throw RuntimeException("오류 발생!")
}
```
**출력 결과:**  
```
예외 발생: 오류 발생!
```
> - `try-catch` 를 사용하면 **비동기 코드에서도 예외를 안전하게 처리 가능**.  

---

## **8. 정리**
| 기능 | 자바 | 코틀린 |
|------|------|------|
| 비동기 실행 | 콜백 기반 | 코루틴 기반 (`launch`, `async`) |
| 가독성 | 콜백 중첩 발생 | 순차적 코드 스타일 |
| 예외 처리 | 예외 전달 어려움 | `try-catch` 사용 가능 |
| 실행 효율 | 스레드 사용 | 가벼운 코루틴 사용 |

> **코루틴을 사용하면:**  
> ✅ **비동기 코드가 더 간결하고 직관적**  
> ✅ **콜백 지옥 없이 순차적 스타일로 작성 가능**  
> ✅ **스레드보다 가볍고 효율적**  

코틀린에서 **비동기 프로그래밍을 쉽게 구현하려면 코루틴을 적극 활용하자!** 🚀