파이썬 코루틴 심층 가이드: 구현 방법과 비동기 프로그래밍

목차

  • 1. 코루틴이란?
  • 1.1 greenlet을 이용한 코루틴 구현
  • 1.2 yield 키워드를 활용한 코루틴
  • 1.3 asyncio 모듈을 사용한 코루틴
  • 1.4 async와 await 키워드로 구현한 코루틴
  • 2. 코루틴의 중요성
  • 3. 비동기 프로그래밍
  • 3.1 이벤트 루프
  • 3.2 실제 적용 사례
  • 3.3 await 키워드의 활용

1. 코루틴이란?

코루틴은 컴퓨터가 기본적으로 제공하는 프로세스나 스레드가 아닙니다. 코루틴은 개발자가 만든 사용자 수준에서 상태 전환을 하는 경량 프로세스로, 단일 스레드 내에서 여러 작업을 번갈아 가며 실행합니다.

코루틴을 구현하는 몇 가지 방법은 다음과 같습니다:

  • greenlet: 초기에 사용되던 모듈
  • yield 키워드: 코드 실행 상태를 저장할 수 있음
  • asyncio 데코레이터 (Python 3.4 이상)
  • async, await 키워드 (Python 3.5 이상) [권장 방법]

1.1 greenlet을 이용한 코루틴 구현

pip install greenlet

from greenlet import greenlet

def worker_a():
    print("작업자 A 시작")
    gr_b.switch()  # 작업자 B로 전환
    print("작업자 A 종료")

def worker_b():
    print("작업자 B 시작")
    gr_a.switch()  # 작업자 A로 전환
    print("작업자 B 종료")

# greenlet 객체 생성
gr_a = greenlet(worker_a)
gr_b = greenlet(worker_b)

# 작업자 A 실행 시작
gr_a.switch()

1.2 yield 키워드를 활용한 코루틴

def generator_func():
    yield "첫 번째 값"
    yield from sub_generator()
    yield "마지막 값"

def sub_generator():
    yield "서브 생성자 값 1"
    yield "서브 생성자 값 2"

# 생성자 실행
gen = generator_func()
for value in gen:
    print(value, end=" ")

# 출력 결과
# 첫 번째 값 서브 생성자 값 1 서브 생성자 값 2 마지막 값

1.3 asyncio 모듈을 사용한 코루틴

Python 3.4 버전부터 표준 라이브러리에 포함되어 있으며, 별도 설치가 필요하지 않습니다.

import asyncio

@asyncio.coroutine
def process_a():
    print("프로세스 A 실행")
    # IO 작업이 발생하면 자동으로 다른 작업으로 전환
    yield from asyncio.sleep(2)
    print("프로세스 A 종료")

@asyncio.coroutine
def process_b():
    print("프로세스 B 실행")
    yield from asyncio.sleep(2)
    print("프로세스 B 종료")

# 두 코루틴 함수를 tasks 리스트로 패키징
tasks = [
    asyncio.ensure_future(process_a()),
    asyncio.ensure_future(process_b())
]

# 이벤트 루프 생성 및 실행
loop = asyncio.get_event_loop()
loop.run_until_complete(tasks)

1.4 async와 await 키워드로 구현한 코루틴

Python 3.5 이상에서 권장되는 방식입니다.

import asyncio

async def task_a():
    print("작업 A 시작")
    # IO 작업이 발생하면 자동으로 다른 작업으로 전환
    await asyncio.sleep(2)
    print("작업 A 완료")

async def task_b():
    print("작업 B 시작")
    await asyncio.sleep(2)
    print("작업 B 완료")

# 두 코루틴 함수를 tasks 리스트로 패키징
tasks = [
    asyncio.ensure_future(task_a()),
    asyncio.ensure_future(task_b())
]

# 이벤트 루프 생성 및 실행
loop = asyncio.get_event_loop()
loop.run_until_complete(tasks)

# Python 3.7 이상에서는 다음과 같이 간편하게 실행 가능
# asyncio.run(task_a())

2. 코루틴의 중요성

스레드에서 IO 대기 시간이 발생하면, 스레드는 그 자리에서 멈춰서 대기하지 않고 다른 작업을 수행할 수 있습니다. 이것이 바로 프로세스의 비동기 실행입니다.

코루틴을 사용하여 리소스에 요청을 보낼 때는 aiohttp 같은 제3자 라이브러리를 활용할 수 있습니다.

3. 비동기 프로그래밍

3.1 이벤트 루프

이벤트 루프는 특정 코드를 감시하고 실행하는 데 사용되는 무한 루프와 같습니다.

# 의사 코드
작업_리스트 = [작업1, 작업2, ...]
while True:
  for 준비된_작업 in 실행_가능_작업_리스트:
    실행
  for 완료된_작업 in 완료된_작업_리스트:
    작업_리스트에서_삭제

import asyncio 

# 이벤트 루프 생성
loop = asyncio.get_event_loop()
# 작업을 작업 리스트에 추가
loop.run_until_complete(작업)

3.2 실제 적용 사례

코루틴 함수: 함수를 정의할 때 async def 함수명 형식을 사용합니다.

코루틴 객체: 코루틴 함수를 실행하면 코루틴 객체가 생성됩니다.

async def sample_coroutine():
    pass
coro_obj = sample_coroutine()

중요한 점: 코루틴 함수를 실행하면 생성되는 코루틴 객체 내부의 코드는 즉시 실행되지 않습니다.

import asyncio

async def greeting():
    print("안녕하세요")

coro = greeting()  # 코루틴 객체

# 이벤트 루프를 통해 코루틴 객체 실행
loop = asyncio.get_event_loop()
loop.run_until_complete(coro)

# Python 3.7 이상에서는 간단하게 실행 가능
# asyncio.run(coro)

3.3 await 키워드의 활용

await + 대기 가능한 객체 {코루틴 객체, Future 객체, Task 객체} (IO 대기와 유사)

import asyncio

async def demo():
    print("시작")
    result = await asyncio.sleep(2)
    print(result)

asyncio.run(demo())

await는 객체의 값이 결과를 반환할 때까지 기다린 후 다음 코드를 계속 실행합니다.

import asyncio

async def background_task():
    print("백그라운드 작업 시작")
    await asyncio.sleep(2)
    print("백그라운드 작업 완료")
    return "완료됨"

async def main():
    print("메인 함수 실행")
    # IO 작업이 발생하면 현재 코루틴 작업을 일시 중단하고, IO 작업이 완료된 후 계속 진행
    # 현재 코루틴이 중단되는 동안 이벤트 루프는 다른 코루틴 작업을 실행
    result = await background_task()
    print("IO 작업 완료, 결과:", result)

asyncio.run(main())

# 실행 결과
# 메인 함수 실행
# 백그라운드 작업 시작
# 백그라운드 작업 완료
# IO 작업 완료, 결과: 완료됨

태그: 파이썬 코루틴 비동기 프로그래밍 asyncio async

7월 6일 22:18에 게시됨