Notice
Recent Posts
Recent Comments
«   2026/07   »
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
관리 메뉴

훌륭한 개발자가 되기 위하여

동기와 비동기, Thread와 Coroutine 그리고 여러 가지 패턴 본문

Kotlin

동기와 비동기, Thread와 Coroutine 그리고 여러 가지 패턴

jay20033 2024. 7. 28. 03:37

동기와 비동기

동기 : 어떤 요청을 보낸 뒤, 그 요청의 결과값을 얻기까지 작업을 멈추는 것

 

특징

 

  • 순차적 처리: 요청을 보내면 결과가 반환될 때까지 기다립니다.
  • 작업 차단: 요청이 완료될 때까지 다음 작업을 수행하지 않습니다.
  • 단순성: 코드의 흐름이 직관적이며, 이해하기 쉽습니다.
  • 응답 시간: 결과를 기다리는 동안 아무것도 하지 않기 때문에 응답 시간이 길어질 수 있습니다.

 

비동기 : 어떤 요청을 보낸 뒤, 그 요청의 결과값이 오기까지 멈추지 않고 또 다른 일을 수행하는 것

 

특징

 

  • 병렬적 처리: 요청을 보내고, 결과를 기다리지 않으며, 다른 작업을 계속 수행합니다.
  • 작업 비차단: 결과가 준비되면 알림을 받거나 콜백 함수가 호출됩니다.
  • 복잡성: 코드의 흐름이 복잡해질 수 있으며, 콜백이나 비동기 제어 구조를 사용해야 합니다.
  • 효율성: CPU 자원을 효율적으로 사용할 수 있으며, 긴 작업이 진행되는 동안 다른 작업을 병렬로 수행할 수 있습니다.

 


Process : 보조기억장치의 프로그램이 메모리 상으로 적재되어 실행되면 프로세스가 된다.

Thread : 같은 Process 내에서 실행되는 여러 작업 (흐름)의 단위

Thread와 Coroutine의 공통점

동시성 (Concurrency)

동시에 여러 작업을 수행하는 것이다.

-> 시분할 기법을 활용하여 그림과 같이 여러 작업을 조금씩 나누어서 번갈아가며 실행하는 것이다.

병렬성 (Parallelism)

시분할 기법없이 여러 작업을 한 번에 동시에 수행하는 것인데 자원(CPU 코어)의 입장에선 자기는 자기 할 일 1개를 하는 것이다. -> 병렬성은 자원(CPU 코어)이 여러 개 일 때 가능하다.


Thread와 Coroutine의 차이점

Thread

  • 작업의 단위 : Thread
    • 각 Thread가 독립적인 Stack 메모리 영역을 가짐
  • 동시성 보장 수단 : Context Switching
    • 운영체제 커널에 의한 Context Switching을 통해 동시성 보장
    • 블로킹 (Blocking) : Thread A가 Thread B의 결과가 나오기까지 기다려야 한다면, Thread A은 Thread B의 결과가 나올 때 까지 해당 자원을 못 씀

 

Thread A 에서 Task 1 을 수행하다가 Task 2 의 결과가 필요할 때, 비동기적으로 Thread B 를 호출을 하게 된다. 이 때 Thread A 는 블로킹되고, Thread B 로 Context Switching 이 일어나 Task 2 를 수행한다. Task 2 가 완료되면, 다시 Thread A 로 Context Switching 이 일어나 결과값을 Task 1 에 반환한다.


Thread의 구성요소

  • ThreadGroup : 여러 Thread를 하나의 그룹으로 묶어서 관리할 수 있는 클래스
  • Runnable : 실행될 코드 블록을 정의하는 인터페이스 / run 메서드를 구현하여 실행될 작업을 정의한다.
  • ExecutorService : ThreadPool을 관리하여 Thread 생성을 최적화하는 클래스
  • Future : 비동기 작업의 결과를 나타내는 객체로 작업이 완료되었는지 여부를 확인하고 결과를 가져올 수 있다.
  • Synchronization : Thread 간의 자원 접근을 조율하여 동기화 문제를 방지하는 메커니즘
    • Synchronized 키워드 또는 Lock 클래스를 사용하여 구현

Coroutine

  • 작업 하나하나의 단위 : Coroutine Object
    • 여러 작업 각각에 Object 를 할당함
    • Coroutine Object 도 엄연한 객체이기 때문에 JVM Heap 에 적재 됨 (Kotlin 기준)
  • 동시성 보장 수단 : Programmer Switching (No-Context Switching)
    • 프로그래머의 코드를 통해 Switching 시점을 마음대로 정함 (OS 관여 X)
    • Suspend (Non-Blocking) : Object 1 이 Object 2 의 결과가 나오기까지 기다려야 한다면, Object 1 은 Suspend 되지만, Object 1 을 수행하던 Thread 는 그대로 유효하기 때문에 Object 2 도 Object 1 과 동일한 Thread 에서 실행될 수 있음

 

작업 단위는 Coroutine Object 이므로, Task 1 을 수행하다가 비동기 작업 Task 2 가 발생하더라도, Context Switching 없이 같은 Thread 에서 Task 2 를 수행할 수 있고, 맨 오른쪽 경우처럼 하나의 Thread 에서 여러 Coroutine Object 들을 같이 수행할 수도 있다. 한 쓰레드에서 다수의 Coroutine 을 수행할 수 있고, Context Switching 이 필요없는 특성에 따라 Coroutine 을 Light-weight Thread 라고 부르는 것이다.


코루틴의 구성요소

  • CoroutineScope: 하나 이상의 관련 코루틴을 관리, 모든 코루틴은 해당 범위 내에서 실행해야 한다.
    • 종류: CoroutineScope, LifecycleScope, ViewModelScope
  • launch: 코루틴을 만들고 함수 본문의 실행을 해당하는 디스패처에 전달하는 함수
  • Dispatcher: 코루틴이 어느 스레드에서 실행될지 결정하는 역할
    • Dispatchers.Main : UI와 상호작용하고 빠른 작업을 실행하기 위해서만 사용해야 한다.
    • Dispatchers.IO : 메인 스레드 외부에서 디스크 또는 네트워크 I/O를 실행하도록 최적화되어 있다.
    • Dispatchers.Default : CPU를 많이 사용하는 작업을 실행하는데 최적화되어 있다. (list sort, Json parsing..)

Coroutine은 Thread의 대안이 아니라, Thread를 더 잘게 쪼개어 사용하기 위한 개념이다.

작업의 단위를 Object로 축소하면서 하나의 Thread다수의 코루틴을 다룰 수 있기 때문에, 작업 하나하나에 Thread를 할당하여 메모리 낭비, Context Switching 비용 낭비를 할 필요가 없음


싱글톤 패턴

객체의 인스턴스를 1개만 생성하여 계속 재사용하는 패턴

장점 

  • 정적 메모리 영역을 사용하기 때문에 추후 해당 객체에 접근할 때 메모리 낭비를 방지할 수 있다.
  • 전역 데이터처럼 사용할 수 있어 쉽게 접근하고 공유하기 쉽다.
  • 공통된 여러 객체를 생성하여 사용하는 경우 편하다.

단점

  • 동시성 문제가 발생할 수도 있다. -> 싱글톤 객체에 대한 동시접근 문제
  • 테스트 어려움 : 전역 상태를 가지기 때문에 단위 테스트가 어려울 수 있다.
class SingleTon private constructor() {
    companion object {
        private val instance = SingleTon()
        fun sharedInstance(): SingleTon = instance
    }
}

 


Publisher-Subscriber 패턴

발행자와 구독자가 있고, 그 사이에 브로커(=메시지 큐)가 존재하는 형태이다.

 

특징

  • 발행자 메시지의 수진자가 정해져 있지 않다.
  • 메시지는 정해진 범주에 따라서 구독을 신청한 수신자에게 전달이 된다.
  • 수신자는 발행자에 대한 정보 없이, 원하는 메시지를 수신할 수 있다.

발행자(=pub)와 구독자(=sub)

1. pub이 sub의 선언 위치나 존재를 알 필요가 없다.

  • 메시지 큐와 같은 브로커 역할을 하는 중간 지점에 메시지를 던져 두기만 하면, 브로커가 알아서 처리한다.

2. sub 역시 pub의 선언 위치나 존재를 알 필요가 없다.

  • 브로커에 할당된 작업만을 모니터링하고, 원하는 작업이 발생하면 할당받아 작업을 수행하면 된다.

브로커

브로커는 발행자와 구독자 사이에 위치하고 있다.

 

메시지 큐

  • 주로 메시지큐가 브로커로서의 역할을 수행하며, 두 객체 사이에서 구독과 발행 이후의 메시지를 처리해준다.
  • 브로커가 모든 들어오는 메시지를 필터링하며, 타겟들(=구독자)에게 메시지를 배포하는 역할을 한다.
  • 다른 곳의 API로부터 데이터 송수신이 가능하다.
  • 다양한 앱과 비동기 통신이 가능하다.
  • 이메일 발송, 문서 업로드가 가능하다.
  • 많은 양의 프로세스를 처리 가능하다.

장점

  • 비동기(Asynchronous) : 큐에 넣어서 나중에 처리 가능
  • 비동조(Decoupling) : 앱과 분리 가능
  • 탄력성(Resilience) : 일부 실패가 전체에 영향x
  • 과잉(Redunadancy) : 실패할 경우 재실행 가능
  • 보증(Guarantees) : 작업 처리 확인 가능
  • 확장성(Scalable) : 다수 프로세스들이 큐에 메시지 보내기 가능

단점

  • '큐'이기 때문에 사용자가 많아지거나, 데이터가 많아지면 요청에 대한 응답이 늦어지게 된다.
  • 즉 과도한 트래픽이 몰리게 되면, 대기 시간 지연으로 인해 서비스가 망가질 위험이 있다.

Observer 패턴과 Pub-Sub 패턴의 차이점

1. Observer 패턴은 Observer와 Subject 간 서로를 알고 있으나, Pub-Sub 패턴은 서로를 몰라도 상관없다.

Pub-Sub 패턴은 발행 객체와 구독 객체 사이에 직접적인 소통을 하지 않아도 된다.
소통의 역할을 브로커라고 하는 제 3의 구성요소가 수행하기 때문이다.

 

브로커는 옵저버와 옵저버블 사이에서 메시지를 필터링하여 다시 배포하는 역할을 한다.
그러므로 옵저버와 옵저버블은 서로를 알지 못해도 브로커에 의해 소통 할 수 있다.

2. Pub-Sub 패턴이 Observer 패턴보다 더 낮은 결합도를 가진다.
3. Observer 패턴은 주로 동기적으로 동작하며, Pub-Sub 패턴은 비동기적으로 동작한다.
4. Observer 패턴은 단일 도메인에서 동작해야 하며 Pub-Sub 패턴은 크로스 도메인 상황에서도 동작이 가능하다.

크로스 도메인이란, 네이버나 카카오 같은 서로 다른 도메인을 의미한다.
또는 API 서버, 파일 서버, 소켓 서버 등 동일한 사이트에서 다른 목적으로 서버를 나눈 경우에도 크로스 도메인에 속한다.

 

발행구독 패턴에서 pub, sub은 서로를 몰라도 된다는 점을 이용하는 것이다.
pub, sub이 각각 브로커와 소통할 수 있다면, 앱의 도메인이 다르더라도 처리가 가능하기 때문이다.

'Kotlin' 카테고리의 다른 글

파일데이터 시스템  (0) 2024.08.05
비동기 처리  (0) 2024.07.29
객체 지향 프로그래밍(OOP)에 대하여  (0) 2024.07.28
함수형 프로그래밍과 Sealed Class  (2) 2024.07.25
예외 처리란?  (0) 2024.07.15