Curieux.JY
  • Post
  • Note
  • Jung Yeon Lee

On this page

  • 🧩 기본 개념
  • ⚙️ 예시 비교
    • (1) 일반 함수 (def)
    • (2) 비동기 함수 (async def)
  • ⏱️ 언제 async def를 써야 할까?
  • 🚀 동기 vs 비동기 실행 데모
  • 🔍 결과 해석

📝Async vs Sync in Python

python
async
2025
Async def에 대해 알아보기
Published

October 20, 2025

async def와 def의 차이는 함수의 실행 방식(동기 vs 비동기)에 있어요.

🧩 기본 개념

구분 def (일반 함수) async def (비동기 함수)
실행 방식 동기(Synchronous) 비동기(Asynchronous)
반환 값 일반적인 값 (예: int, list) 코루틴 객체 (coroutine)
실행 시점 즉시 실행되어 결과를 반환 실행되지만 await로 기다려야 실제 결과를 얻음

⚙️ 예시 비교

(1) 일반 함수 (def)

def greet():
    return "Hello"

result = greet()
print(result)
Hello

def는 호출되면 바로 실행되고 결과를 반환해요.

(2) 비동기 함수 (async def)

import asyncio

async def greet():
    return "Hello"

result = greet()
print(result)
<coroutine object greet at 0x7ea4903cb110>
  • async def로 정의된 함수는 즉시 실행되지 않음
  • 대신 “코루틴(coroutine)” 객체를 반환
  • 실제 실행하려면 await 또는 asyncio.run()을 써야 함

await로 실행하기

import asyncio  # ✅ asyncio: 파이썬의 비동기(Asynchronous) 프로그래밍을 지원하는 표준 모듈

# ============================================================
# ① 코루틴(Coroutine) 정의
# ============================================================
# async def 로 정의된 함수는 일반 함수처럼 즉시 실행되지 않음.
# 호출 시 '코루틴 객체(coroutine object)'를 반환하고,
# 이 객체를 이벤트 루프(event loop)가 스케줄링해서 실제로 실행함.
async def greet():
    # 이 함수는 단순히 "Hello" 문자열을 반환하는 비동기 함수
    return "Hello"


# ============================================================
# ② 메인 코루틴 (다른 코루틴을 호출하고 관리)
# ============================================================
async def main():
    # await 키워드는 '코루틴 실행 결과를 기다린다'는 의미.
    # greet()를 호출하면 즉시 실행되는 게 아니라 코루틴 객체를 반환함.
    # await greet()는 이벤트 루프에게 "이 코루틴을 실행하고 끝날 때까지 기다려줘"라고 요청함.
    result = await greet()

    # greet()의 실행이 완료되면, result에는 "Hello"가 저장됨.
    print(result)  # 콘솔에 "Hello" 출력


# ============================================================
# ③ 이벤트 루프(Event Loop)
# ============================================================
# asyncio.run()은 파이썬 3.7부터 도입된 고수준 API로,
# 내부적으로 다음과 같은 과정을 수행함:
#
#  1️⃣ 새로운 이벤트 루프 생성
#  2️⃣ main() 코루틴을 루프에 등록하고 실행 시작
#  3️⃣ await 구문 등을 통해 다른 코루틴들을 스케줄링하여 실행 순서를 관리
#  4️⃣ 모든 코루틴이 완료되면 루프를 정리하고 종료
#
# 즉, 프로그램 전체의 비동기 작업을 총괄하는 "조정자" 역할을 함.
asyncio.run(main())

📌 실행 결과

Hello

이벤트 루프 내부 구성을 나타내면 아래와 같습니다.

flowchart TD
    %% =============================
    %% asyncio (High-level)
    %% =============================
    subgraph A["asyncio (고수준 API)"]
        A1["asyncio.run(main)"]
        A2["루프 생성 및<br>run_until_complete(Task(main))"]
        A3["Task<br>코루틴 실행 단위<br>(상태/콜백/스택 보유)"]
        A4["Future<br>결과·예외 저장, 콜백 등록 가능"]

        A1 --> A2
        A2 --> A3
        A2 --> A4
    end

    %% =============================
    %% Event Loop 내부 구조
    %% =============================
    subgraph B["Event Loop (단일 스레드, 협력형 전환)"]
        direction LR
        B1["Ready Queue"]
        B2["Callback Queue"]
        B3["실행 단계<br>(한 틱씩 Task 실행)"]
        B5["Selector / IO<br>(epoll, kqueue)"]
        B6["타이머 힙<br>(지연 콜백, sleep)"]

        B1 <-->|"콜백 이동"| B2
        B2 -->|"run_ready()"| B3
        B5 <-->|"poll(timeout)"| B3
        B3 --> B6
    end

    %% =============================
    %% Control Flow
    %% =============================
    A3 -. "await" .-> B3
    B3 -. "I/O 완료·타이머 만료" .-> B1
    B1 -->|"Task 재개"| A3



⏱️ 언제 async def를 써야 할까?

비동기는 I/O 작업(입출력)이 오래 걸리는 아래와 같은 상황에서 유용합니다.

  • 네트워크 요청 (HTTP request)
  • 파일 읽기/쓰기
  • 데이터베이스 쿼리
  • 대기 시간(sleep)이 많은 코드
import asyncio

async def download():
    print("다운로드 시작")
    await asyncio.sleep(2)   # 네트워크 대기
    print("다운로드 완료")

async def main():
    await asyncio.gather(download(), download(), download())

asyncio.run(main())

📌 실행 결과

다운로드 시작
다운로드 시작
다운로드 시작
(2초 후)
다운로드 완료
다운로드 완료
다운로드 완료

세 개가 동시에 실행되어 총 2초만 걸려요! 만약 def였다면 순서대로 실행되어 6초가 걸렸을 겁니다.

항목 def async def
실행 시점 즉시 실행 코루틴 객체 반환
반환 값 일반 값 coroutine
결과 받기 바로 가능 await 필요
주 용도 CPU 연산 중심 코드 I/O 중심(대기 많은) 코드
병렬 처리 어렵다 asyncio.gather() 등으로 쉽게 가능

🚀 동기 vs 비동기 실행 데모

import asyncio
import time

# ------------------ 동기 (def) ------------------
def sync_task(name):
    print(f"{name} 시작")
    time.sleep(2)
    print(f"{name} 완료")

def sync_main():
    start = time.time()
    for i in range(3):
        sync_task(f"작업 {i+1}")
    print(f"총 소요 시간(동기): {time.time() - start:.2f}초")

# ------------------ 비동기 (async def) ------------------
async def async_task(name):
    print(f"{name} 시작")
    await asyncio.sleep(2)
    print(f"{name} 완료")

async def async_main():
    start = time.time()
    await asyncio.gather(*[async_task(f"작업 {i+1}") for i in range(3)])
    print(f"총 소요 시간(비동기): {time.time() - start:.2f}초")

print("\n=== 동기 실행 ===")
sync_main()

print("\n=== 비동기 실행 ===")
# 이미 실행 중인 루프에서 안전하게 실행 (top-level await)
await async_main()

=== 동기 실행 ===
작업 1 시작
작업 1 완료
작업 2 시작
작업 2 완료
작업 3 시작
작업 3 완료
총 소요 시간(동기): 6.00초

=== 비동기 실행 ===
작업 1 시작
작업 2 시작
작업 3 시작
작업 1 완료
작업 2 완료
작업 3 완료
총 소요 시간(비동기): 2.00초

🔍 결과 해석

  • 동기(def): 1→2→3 순차 실행 → 약 6초
  • 비동기(async def): 3개 동시 실행 → 약 2초

비동기는 대기(I/O) 시간이 많은 작업에서 큰 이점을 제공합니다.

Copyright 2024, Jung Yeon Lee