CS 지식

[CS 지식13.] 동기 및 비동기 처리란?

Somaz 2024. 6. 6. 11:31
728x90
반응형

Overview

오늘은 프로그램의 흐름 제어에서 핵심 개념 중 하나인 동기(Synchronous)비동기(Asynchronous) 처리 방식에 대해 알아보자.

 

이 두 개념은 입출력 처리, API 호출, 파일 작업, 네트워크 요청 등 다양한 상황에서 프로그램이 작업을 어떻게 처리할지를 결정짓는 매우 중요한 기준이다.

 

특히 Python과 같은 현대 프로그래밍 언어에서는 async/await, Promise, Coroutine 등의 비동기 문법이 본격적으로 사용되고 있어, 이를 명확하게 이해하는 것이 실무에서도 큰 도움이 된다.

출처 : https://www.mendix.com/blog/asynchronous-vs-synchronous-programming/

 

 

 

 

📅 관련 글

2023.01.13 - [CS 지식] - [CS 지식1.] 웹 브라우저의 동작원리

2023.02.23 - [CS 지식] - [CS 지식2.] DNS의 동작원리(Domain Name System)

2023.03.06 - [CS 지식] - [CS 지식3.] HTTP / HTTPS 란?

2023.03.07 - [CS 지식] - [CS 지식4.] OSI 7계층 & TCP/IP 4계층이란?

2023.03.17 - [CS 지식] - [CS 지식5.] 가상화란?

2023.05.24 - [CS 지식] - [CS 지식6.] HTTP 메서드(Method)란? / HTTP Status Code

2023.12.05 - [CS 지식] - [CS 지식7.] Kubernetes 구성요소와 Pod 생성 방식이란?

2023.12.19 - [CS 지식] - [CS 지식8.] 프로세스(Process)와 스레드(Thread)란?

2023.12.30 - [CS 지식] - [CS 지식9.] 클라우드 컴퓨팅이란?(Public & Private Cloud / IaaS SaaS PaaS / Multitenancy)

2024.01.05 - [CS 지식] - [CS 지식10.] 웹1.0(Web1.0) vs 웹2.0(Web2.0) vs 웹3.0(Web3.0)

2024.02.02 - [CS 지식] - [CS 지식11.] NAT(Network Address Translation)란?

2024.05.22 - [CS 지식] - [CS 지식13.] 동기 및 비동기 처리란?

2024.05.23 - [CS 지식] - [CS 지식14.] 3tier 아키텍처란?

2024.08.28 - [CS 지식] - [CS 지식15.] SSR vs CSR vs ISR vs SSG

2024.11.09 - [CS 지식] - [CS 지식16.] stdin(표준입력) vs stdout(표준출력) vs stderr(표준에러)

2024.11.11 - [CS 지식] - [CS 지식17.] IPsec vs SSL/TLS

2024.11.22 - [CS 지식] - [CS 지식18.] Quantum Computing(양자 컴퓨팅)

 

 

 

 

 


 

 

동기 처리(Synchronous Processing)

동기 처리에서는 작업이 한 번에 하나씩 순서대로 완료된다. 이는 다음 작업을 시작하기 전에 작업을 완료해야 함을 의미한다. 이러한 유형의 처리는 간단하고 이해하기 쉽다.

 

 

동기 처리의 특징

  • 차단(Blocking): 각 작업은 다음 작업이 시작되기 전에 완료되어야 하며, 실행중인 작업이 완료될 때까지 후속 작업을 차단한다.
  • 선형 실행(Linear Execution): 작업은 코드에 나타나는 정확한 순서대로 실행된다.
  • 단순성(Simplicity): 작업 실행의 순차적 특성으로 인해 프로그래밍 및 디버깅이 더 쉽다.
  • 사용 예(Usage Examples): 파일 읽기 또는 쓰기, 데이터베이스 트랜잭션 또는 후속 작업이 이전 작업의 결과에 의존하는 모든 작업에 사용한다.

 

 

 

동기 예제: 파일 읽기

동기 예제에서 Python은 두 개의 텍스트 파일을 순서대로 읽는다. 프로그램은 두 번째 파일 읽기를 시작하기 전에 첫 번째 파일이 완전히 읽힐 때까지 기다린다.

 

 

txt 파일을 먼저 생성한다.

cat <<EOF > somaz1.txt
Hello Somaz1
EOF

cat <<EOF > somaz2.txt
Hello Somaz2
EOF

 

 

그리고 sync-test.py 를 생성해준다.

cat <<EOF > sync-test.py
def read_file(file_name):
    with open(file_name, 'r') as file:
        print(f"Reading {file_name}...")
        data = file.read()
        print(f"Finished reading {file_name}.")
        print(f"Contents of {file_name}: \\\\n{data}\\\\n")
        return data

def main():
    data1 = read_file('somaz1.txt')
    data2 = read_file('somaz2.txt')
    print("Both files have been read.")

if __name__ == "__main__":
    main()
EOF

 

 

실행해보면 아래와 같은 결과가 나온다.

python3 sync-test.py
Reading somaz1.txt...
Finished reading somaz1.txt.
Contents of somaz1.txt:
Hello Somaz1

Reading somaz2.txt...
Finished reading somaz2.txt.
Contents of somaz2.txt:
Hello Somaz2

Both files have been read.

 

 

 

 

 

 


 

 

 

 

 

 

비동기 처리(Asynchronous Processing)

비동기 처리를 통해 작업을 기본 프로그램 흐름과 독립적으로 실행할 수 있으므로 백그라운드에서 작업을 처리할 수 있다. 이러한 유형의 처리는 웹 서버나 사용자 인터페이스를 갖춘 애플리케이션과 같이 작업에 상당한 시간이 걸릴 수 있는 환경에 필수적이다.

 

Python의 비동기 실행은 Python 3.5부터 표준 라이브러리의 일부인 asyncio 라이브러리를 사용하여 수행된다. 이 모델을 사용하면 코드의 특정 부분을 동시에 실행할 수 있으며, 이는 I/O 바인딩 및 상위 수준의 구조화된 네트워크 코드에 특히 유용하다.

 

Python은 asyncawait 구문을 사용하여 비동기 함수를 정의하므로 기존 콜백 기반 접근 방식에 비해 비동기 코드를 더 쉽게 작성하고 유지 관리할 수 있다.

 

 

 

 

비동기 처리의 특징

  • 비차단(Non-blocking): 작업은 독립적으로 시작하고 완료할 수 있으며, 프로그램은 작업을 시작하고 완료될 때까지 기다리지 않고 계속 진행할 수 있다.
  • 동시 실행(Concurrent Execution): 여러 작업을 병렬로 처리할 수 있어 애플리케이션의 효율성과 응답성이 향상된다.
  • 복잡성(Complexity): 경쟁 조건 및 교착 상태와 같은 잠재적인 문제로 인해 프로그래밍 및 디버그가 더 어렵다.
  • 사용 예(Usage Examples): API 요청, 파일 업로드 또는 사용자 인터페이스를 정지하거나 다른 계산을 지연시키고 싶지 않은 장기 실행 I/O 작업에 사용한다.

 

 

 

 

비동기 예제: 파일 읽기

비동기 예제에서 Python은 비동기 파일 작업을 위해 aiofiles 라이브러리를 사용하여 두 개의 텍스트 파일을 동시에 읽는 비차단 접근 방식을 사용한다. 이를 통해 프로그램은 첫 번째 파일이 완료될 때까지 기다리지 않고도 두 번째 파일 읽기를 시작할 수 있다. 이 방법은 프로그램이 파일 읽기 프로세스가 완료되기를 기다리는 동안 다른 작업을 수행할 수 있는 I/O 바인딩 작업에 특히 효율적이다.

 

 

aiofiles가 아직 설치되지 않은 경우 설치한다.

pip install aiofiles

 

 

이제 두 개의 텍스트 파일을 읽는 비동기 Python 스크립트를 작성해 보겠다.

cat <<EOF > rsync-test.py
import aiofiles
import asyncio

async def read_file(file_name):
    print(f"Starting to read {file_name}")
    async with aiofiles.open(file_name, 'r') as file:
        content = await file.read()
        print(f"Finished reading {file_name}")
        print(f"Contents of {file_name}: \\n{content}\\n")

async def main():
    tasks = [read_file('somaz1.txt'), read_file('somaz2.txt')]
    await asyncio.gather(*tasks)
    print("Both files have been read.")

if __name__ == "__main__":
    asyncio.run(main())
EOF

 

 

실행해보면 아래와 같은 결과가 나온다. 다음 작업을 시작하기 전에 하나가 완료될 때까지 기다리지 않고 somaz1.txt와 somaz2.txt를 동시에 읽는다. 이는 여러 I/O 작업을 처리해야 하고 단일 파일 액세스로 인해 차단되지 않는 이점을 얻을 수 있는 애플리케이션에 유용하다.

#1
python3 rsync-test.py
Starting to read somaz1.txt
Starting to read somaz2.txt
Finished reading somaz2.txt
Contents of somaz2.txt:
Hello Somaz2

Finished reading somaz1.txt
Contents of somaz1.txt:
Hello Somaz1

Both files have been read.

#2
python3 rsync-test.py
Starting to read somaz1.txt
Starting to read somaz2.txt
Finished reading somaz1.txt
Contents of somaz1.txt:
Hello Somaz1

Finished reading somaz2.txt
Contents of somaz2.txt:
Hello Somaz2

Both files have been read.

 

 

 

 

요약정리

동기 처리와 비동기 처리 사이의 선택은 애플리케이션 요구 사항에 따라 달라진다.

  • 동기: 작업을 특정 순서로 완료해야 하거나 다음 작업에 각 작업의 출력이 필요할 때 사용
  • 비동기: 특히 작업이 독립적이거나 병렬로 수행될 수 있는 경우 애플리케이션의 처리량과 응답성을 향상시키는 데 사용

 

 

 

 

 


 

 

 

동기와 비동기를 더 깊게 이해하고 싶다면?

 

 

 

동기 vs 비동기 비교 표

 

 

구분 동기(Synchronous) 비동기(Asynchronous)
실행 순서 순차적으로 하나씩 처리 병렬/동시 실행 가능
블로킹 여부 블로킹 (다음 작업 대기) 논블로킹 (다음 작업과 병행 가능)
코드 단순성 상대적으로 단순 상대적으로 복잡 (콜백, await 등 필요)
에러 처리 try/except로 직관적으로 처리 콜백 내부 또는 await 문에서 처리 필요
사용 사례 간단한 로직, CLI, 파일 입출력 웹 서버, 대규모 I/O, 네트워크 통신
언어 지원 거의 모든 언어에서 기본 지원 최신 언어에서 키워드로 지원 (async/await 등)

 

 

 

실제 웹 서버 흐름 예시: Flask vs FastAPI

# Flask 동기 예시
from flask import Flask
import time

app = Flask(__name__)

@app.route("/sync")
def sync_route():
    time.sleep(3)  # 블로킹
    return "동기 완료!"

# FastAPI 비동기 예시
from fastapi import FastAPI
import asyncio

app = FastAPI()

@app.get("/async")
async def async_route():
    await asyncio.sleep(3)  # 논블로킹
    return {"message": "비동기 완료!"}
  • 결과: FastAPI는 요청이 동시에 여러 개 들어와도 asyncio 덕분에 처리가 병렬적으로 가능!
  • 반면 Flask는 하나의 요청 처리 중 나머지 요청은 대기함.

 

 

 

JS 기준 Promise, async/await 흐름

// Promise 예제
function wait(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

wait(1000).then(() => {
  console.log("1초 후 실행 (Promise)");
});

// async/await 예제
async function run() {
  await wait(1000);
  console.log("1초 후 실행 (async/await)");
}

run();
  • async/await 는 Promise 기반 문법을 더 직관적이고 동기식처럼 작성할 수 있게 해줌!

 

 

 

 

코루틴(Coroutine)이란?

Python에서 async def로 선언한 함수는 코루틴 객체를 반환한다.
이 코루틴은 즉시 실행되지 않고, awaitasyncio.run() 등의 방식으로 스케줄링되어야 실행된다.

import asyncio

async def greet():
    return "안녕하세요!"

# 호출
coroutine_obj = greet()
result = asyncio.run(coroutine_obj)
print(result)
 
  • 코루틴은 경량 스레드처럼 동작하며, 여러 작업을 효율적으로 전환하며 실행할 수 있음!
 
 
 
 
 

 

Node.js 비동기 흐름 도식 (브라우저 vs Node 차이)

 

구성 요소 브라우저 환경 Node.js 환경
Call Stack 동일: 현재 실행 중인 함수 보관 동일
Web APIs DOM, setTimeout, fetch 등 브라우저 제공 API File System, HTTP, Crypto 등 Node 내장 비동기 API
Callback Queue setTimeout, setInterval, 이벤트 핸들러 등 setTimeout, fs.readFile(), process.nextTick()
Microtask Queue Promise.then(), queueMicrotask() Promise.then(), process.nextTick()  등
Event Loop Stack이 비었을 때 Callback Queue에서 작업을 가져와 실행 동일하나, Microtask 우선순위 등 처리 순서 주의

 

주요 차이점

  • 브라우저는 DOM 조작 및 UI 업데이트에 특화
  • Node.js는 파일 시스템, 네트워크, 데이터베이스와의 I/O 중심 비동기 처리에 특화
  • process.nextTick() 은 Node.js에서만 존재하는 Microtask 처리 방식

 

 

 

 

 

Flask에서 gevent, uvicorn, gunicorn 을 통한 비동기 설정 예시

Python은 기본적으로 동기 프레임워크인 Flask를 사용하지만, 아래처럼 비동기 서버와 함께 사용하면 효율적인 비동기 처리가 가능하다.

 

1. gunicorn + gevent

pip install gunicorn gevent
gunicorn -w 4 -k gevent app:app

 

  • -k gevent: 비동기 처리를 위한 워커 타입 지정
  • -w 4: 워커 4개 실행

 

 

 

2. uvicorn (FastAPI에 최적화된 비동기 서버)

pip install uvicorn
uvicorn app:app --host 0.0.0.0 --port 8000

 

  • Flask보단 FastAPI나 Starlette과 함께 사용해야 진가 발휘됨
  • 완전한 async 지원을 제공

 

 

 

 

3. Flask + eventlet 도 대안

pip install eventlet gunicorn
gunicorn -k eventlet -w 1 app:app

 

 

정리

  • Flask 자체는 동기지만, gevent, eventlet, uvicorn 등의 서버를 통해 비동기 구조로 실행 가능
  • 대규모 요청을 처리하거나 I/O 작업이 많은 API라면 반드시 고려해야 함

 

 

 

 

 

Java vs Go: 비동기 처리 방식 비교


 

항목 Java (스레드 기반) Go (고루틴 기반)
기본 실행 단위 Thread (OS 스레드) Goroutine (경량 사용자 스레드)
병렬성 실행 방식 Thread Pool, ExecutorService 사용 Go runtime의 scheduler로 자동 관리
비동기 키워드/프레임워크 CompletableFuture, Reactive Streams, Spring WebFlux go 키워드, 채널(channel), select 등
메모리 비용 스레드당 수 MB 메모리 고루틴당 수 KB 메모리
동시성 제어 synchronized, Lock, Semaphore 등 channel, mutex, waitgroup 등 내장 동시성 도구 제공

 

핵심 요약

  • Java는 전통적인 스레드 기반: 안정적이지만 복잡하고 리소스 부담 ↑
  • Go는 경량 고루틴 기반: 수천 개의 병렬 작업도 손쉽게 실행 가능

 

 

 

 

Gunicorn / Uvicorn 설정 예시 (YAML 스타일)

config.yaml 로 관리하는 Gunicorn 설정 예시

# config.yaml
bind: "0.0.0.0:8000"
workers: 4
worker_class: uvicorn.workers.UvicornWorker
timeout: 120
loglevel: info
accesslog: "-"

 

 

실행 방법

gunicorn -c config.yaml app:app

 

 

TIP

  • worker_class: uvicorn.workers.UvicornWorker → FastAPI, Starlette 기반 앱에 최적
  • 설정 파일로 관리하면 배포 자동화에 유리

 

 

 

 

Go: 채널을 이용한 비동기 처리 예시

package main

import (
    "fmt"
    "time"
)

func fetchData(ch chan string) {
    time.Sleep(2 * time.Second)
    ch <- "📦 데이터 수신 완료"
}

func main() {
    ch := make(chan string)

    go fetchData(ch)

    fmt.Println("⌛ 데이터 기다리는 중...")
    result := <-ch
    fmt.Println(result)
}

 

  • go fetchData(ch) 는 백그라운드 고루틴 실행
  • ch <- "데이터" 는 결과 전송
  • result := <-ch 는 결과를 기다림 (비동기 + 동기 수신)

 

 

고루틴 + 채널 = Go의 비동기 철학

 

 

 

 

Java: CompletableFuture 체이닝 예제

import java.util.concurrent.CompletableFuture;

public class AsyncExample {
    public static void main(String[] args) {
        CompletableFuture.supplyAsync(() -> {
            // 비동기 시작
            System.out.println("🚀 API 호출 중...");
            return "응답 데이터";
        })
        .thenApply(data -> {
            System.out.println("📦 응답 처리: " + data);
            return data.length();
        })
        .thenAccept(length -> {
            System.out.println("✅ 응답 길이: " + length);
        });

        System.out.println("💡 메인 스레드는 계속 실행됨");
        try { Thread.sleep(1000); } catch (InterruptedException e) {}
    }
}

 

  • supplyAsync: 비동기 시작
  • thenApply: 변환 처리
  • thenAccept: 최종 소비
  • Thread.sleep(...) 는 메인 스레드가 너무 빨리 종료되지 않도록 대기

 

📌 CompletableFuture 는 Java 8 이후 등장한 비동기 처리의 핵심 도구

 

 

 

 

 

 

 

 


 

 

 

마무리 

동기와 비동기 프로그래밍은 각각의 목적과 사용 환경이 분명하다.

  • 동기(Sync)는 순차적 흐름과 단순한 에러 핸들링을 제공하며, 복잡도가 낮은 작업에 적합하다.
  • 비동기(Async)는 처리량과 반응성을 최적화할 수 있으며, 특히 I/O 작업이 많은 애플리케이션에 필수적인 접근 방식이다.

 

 

비동기 프로그래밍은 학습 곡선이 있지만, asyncio, aiofiles 같은 툴과 함께 학습하면 훨씬 수월하게 접근할 수 있다.

 

 

 

 

 

 

 


Reference

https://www.mendix.com/blog/asynchronous-vs-synchronous-programming/

https://www.geeksforgeeks.org/difference-between-synchronous-and-asynchronous-transmission/

728x90
반응형