Notice
Recent Posts
Recent Comments
Link
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags more
Archives
Today
Total
관리 메뉴

밤빵's 개발일지

[TIL]20240927 비동기처리와 멀티쓰레드 본문

개발Article

[TIL]20240927 비동기처리와 멀티쓰레드

최밤빵 2024. 9. 28. 00:02

비동기 처리와 멀티쓰레드. 이 두 개념은 자주 사용되며, 동시에 여러 작업을 처리하는 방식에서 중요한 역할을 한다. 하지만 나같은 초입자는 두 개념이 어떻게 다른지, 언제 사용하는 것이 적합한지 혼란스러울 때가 많다. 이번 기회를 통해 비동기 처리와 멀티쓰레드의 차이점과 각각의 장단점을 학습하고자 정리하기로 했다! 

▶비동기 처리란?

비동기 처리는 작업을 요청한 후, 그 작업이 완료될 때까지 기다리지 않고 다른 작업을 수행할 수 있는 방식이다. 작업이 완료된 후에 콜백이나 특정 이벤트가 발생하여 결과를 처리한다. 비동기 처리는 주로 I/O 작업에서 사용되며, 외부 시스템과 통신하는 작업에서 유용하다. 예를 들어, 서버가 데이터베이스에서 데이터를 가져오는 동안 다른 요청을 처리할 수 있는 비동기 방식을 사용하면 효율적으로 여러 작업을 동시에 처리할 수 있다.

 

▷장점:

→ 높은 성능:

비동기 처리는 CPU 자원을 많이 사용하지 않는 작업(I/O 작업 등)을 비동기적으로 처리할 수 있어, 응답성을 높이고 성능을 개선할 수 있다.

→ 효율적인 자원 활용:

비동기 처리를 통해 I/O 바운드 작업에서 불필요한 CPU 대기를 최소화할 수 있다.

 

▷단점:

→ 복잡성 증가:

비동기 방식은 작업 완료 후의 처리를 콜백이나 이벤트로 처리해야 하기 때문에 코드가 복잡해질 수 있다.

→ 디버깅 어려움:

비동기 코드에서는 작업의 흐름이 명확하지 않아 디버깅이 어렵다.

 

▶멀티쓰레드란?

멀티쓰레드는 하나의 프로그램 내에서 여러 개의 쓰레드(Thread)가 동시에 실행되는 방식을 의미한다. 쓰레드는 프로세스 내에서 실행되는 하나의 작업 흐름으로, 멀티쓰레드는 CPU의 멀티코어를 활용해 여러 작업을 병렬로 처리할 수 있다.

멀티쓰레드는 주로 CPU가 바쁘게 여러 작업을 동시에 처리해야 할 때 유용하고, 계산 집약적인 작업에서 효과적이다. 예를 들어, 대규모 데이터를 동시에 처리해야 하는 계산 작업에서 멀티쓰레드를 사용하면 작업 시간을 크게 단축할 수 있다.

 

▷장점:

→ 병렬 처리:

멀티쓰레드를 통해 여러 작업을 동시에 처리할 수 있어, 계산 작업에 매우 효과적이다.

→ 빠른 반응성:

레드 간의 작업이 독립적이기 때문에 빠른 응답성을 제공할 수 있다.

 

▷단점:

→ 동기화 문제:

여러 쓰레드가 동시에 자원을 공유할 때 동기화 문제가 발생할 수 있으며, 이러한 문제를 해결하기 위해 락(lock)과 같은 추가 처리 방법이 필요하다.

→ 높은 메모리 소비:

쓰레드가 많아질수록 시스템 자원을 많이 소비하게 된다.

 

▶비동기 처리와 멀티쓰레드의 차이점

비동기 처리와 멀티쓰레드는 여러 작업을 동시에 처리할 때 자주 사용되지만, 그 동작 방식과 적용되는 상황이 다르다.


구분 비동기 처리 멀티쓰레드
동작 방식 작업 완료를 기다리지 않고 다음 작업 수행 여러 쓰레드가 동시에 작업을 병렬로 처리
주요 사용처 I/O 작업(네트워크 통신, 파일 읽기 등) CPU 집약적 작업(계산, 데이터 처리 등)
성능 I/O 작업에서 성능 최적화 가능 병렬 처리로 CPU 활용 극대화 가능
복잡성 콜백 지옥, 흐름 제어가 어려울 수 있음 동기화 문제 발생 가능, 코드 복잡해짐
자원 소비 적은 자원 사용(I/O 작업에서는 효율적) 쓰레드 수가 많아질수록 메모리 사용 증가
장점 CPU를 효율적으로 사용할 수 있음 계산 집약적인 작업에서 성능 향상
단점 디버깅이 어렵고 코드 복잡성 증가 동기화 문제 발생 가능, 메모리 소비 높음

▶ 비동기 처리와 멀티쓰레드의 선택 기준

→ I/O 바운드 작업에서는 비동기 처리가 더 적합하다. 예를 들어, 파일을 읽거나 네트워크 요청을 처리할 때 CPU 자원을 많이 사용하지 않기 때문에 비동기 처리를 통해 효율적인 처리가 가능하다.

→ CPU 바운드 작업에서는 멀티쓰레드가 더 적합하다. 예를 들어, 대규모 데이터를 처리하거나 복잡한 계산을 수행하는 경우 멀티쓰레드를 사용하여 여러 작업을 동시에 처리함으로써 성능을 극대화할 수 있다.

 

▶ 코드 예시

▷비동기 처리 (자바의 CompletableFuture를 사용한 비동기 코드 예시):

import java.util.concurrent.CompletableFuture;

public class AsyncExample {
    public static void main(String[] args) {
        CompletableFuture.runAsync(() -> {
            try {
                Thread.sleep(2000); // 비동기 작업
                System.out.println("비동기 작업 완료");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("메인 쓰레드는 계속 실행됨");
    }
}

→ CompletableFuture를 이용해 비동기 작업을 수행하는 예시로, 비동기 작업이 실행되는 동안 메인 쓰레드는 중단되지 않고 계속해서 실행된다.

 

▷멀티쓰레드 (쓰레드를 사용한 병렬 처리 예시):

public class MultiThreadExample {
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("쓰레드 1: " + i);
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("쓰레드 2: " + i);
            }
        });

        thread1.start();
        thread2.start();
    }
}

→ 두 개의 쓰레드를 생성하여 병렬로 작업을 수행하는 예시로, 두 쓰레드는 동시에 실행되며, 결과적으로 두 작업이 병렬로 처리된다.

 

▶비동기 처리와 멀티쓰레드의 예외 처리

비동기 처리와 멀티쓰레드를 사용하는 경우, 예외 처리가 일반적인 싱글 스레드 환경과는 다르게 동작한다. 예를 들어, 비동기 코드에서는 예외가 발생해도 메인 스레드로 전파되지 않기 때문에, 별도로 예외를 처리해야 한다.

 

▷비동기 처리에서의 예외 처리

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    if (true) throw new RuntimeException("에러 발생");
});

future.exceptionally(ex -> {
        System.out.println("예외 발생: " + ex.getMessage());
        return null;
        });

→ exceptionally 메서드는 비동기 작업 중 예외가 발생할 경우 이를 처리한다.

 

▷멀티쓰레드에서의 예외 처리

멀티쓰레드에서는 각 쓰레드 내에서 발생한 예외가 메인 스레드로 전달되지 않으므로, 각 쓰레드 내에서 별도로 예외를 처리해야 한다.

Thread thread = new Thread(() -> {
    try {
        // 작업 수행
        throw new RuntimeException("쓰레드 에러 발생");
    } catch (Exception e) {
        System.out.println("예외 처리: " + e.getMessage());
    }
});

thread.start();

 

▶ 비동기와 멀티쓰레드의 실제 적용 사례

비동기 처리와 멀티쓰레드는 각각 어떤 상황에서 주로 사용되는지를 구체적인 예시를 통해 추가로 이해할 수 있다.

 

→ 비동기 처리:

외부 API 호출 시, 응답을 기다리지 않고 다른 작업을 먼저 처리하여 애플리케이션의 응답 속도를 높일 수 있다. 예를 들어, 사용자 정보 조회와 파일 업로드를 비동기로 실행하면, 두 작업을 동시에 진행할 수 있어 응답 시간을 단축할 수 있다.

→ 멀티쓰레드:

대용량 데이터 처리나 이미지 렌더링 등 CPU 자원을 많이 소모하는 작업에서는 여러 스레드를 활용하여 병렬로 작업을 처리함으로써, 성능을 극대화할 수 있다.


▶동시성 이슈와 해결 방법

멀티쓰레드 환경에서 동시성 이슈가 발생할 수 있는데, 여러 쓰레드가 공유 자원에 동시에 접근하여 데이터가 일관성 있게 관리되지 않을 위험이 있다. 이러한 동시성 문제를 해결하기 위해 동기화(synchronization)와 같은 방법이 필요하다.

synchronized 블록을 통해 특정 코드 영역에서 한 번에 한 쓰레드만 접근하도록 제한할 수 있다.

 

ReentrantLock 클래스는 정교한 락 메커니즘을 제공하고, 락 획득 여부를 확인하는 기능을 추가로 제공한다.

public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

→ synchronized 키워드를 통해 increment 메서드가 한 번에 하나의 쓰레드에서만 실행되도록 하여, 동시성 문제를 예방한다.

 

▶비동기 처리와 멀티쓰레드를 결합한 활용

실제 대규모 애플리케이션에서는 비동기 처리와 멀티쓰레드를 결합하여 사용하는 경우가 많다. 예를 들어, 각 요청마다 쓰레드를 생성해 멀티쓰레드 방식으로 처리하면서도, 각 쓰레드 내에서 I/O 작업을 비동기로 처리해 응답 속도를 최적화할 수 있다. Spring 프레임워크에서는 @Async@Scheduled와 같은 어노테이션을 사용하여 비동기 및 멀티쓰레드 작업을 효율적으로 설정할 수 있다.

 

▶결론

두 개념은 동시에 여러 작업을 처리할 때 자주 사용되지만, 각각의 사용 목적과 방식이 다르다. I/O 바운드 작업에는 비동기 처리가, CPU 바운드 작업에는 멀티쓰레드가 더 적합하다. 이를 올바르게 이해하고 적절히 사용함으로써, 백엔드 시스템의 성능을 효율적으로 최적화할 수 있다.