파이썬에서의 비동기 처리: 코루틴과 그 활용

코루틴은 파이썬에서 다중 작업을 구현하는 또 다른 방법으로, 스레드보다 더 작은 실행 단위이며 리소스 사용량도 적습니다. 이는 각 코루틴이 자체적인 프로세서 상태(컨텍스트)를 가지고 있어, 특정 시점에 한 코루틴을 다른 코루틴으로 전환할 수 있음을 의미합니다. 단지 컨텍스트 저장 및 복원만으로도 실행 흐름이 자연스럽게 유지됩니다.

간단히 말해, 하나의 스레드 내부에서 함수가 임의의 지점에서 현재 상태(임시 변수 등)를 저장하고, 다른 함수로 전환하여 실행할 수 있습니다. 이 과정은 일반적인 함수 호출이 아니라, 개발자가 직접 제어하는 방식으로 이루어지며, 언제 다시 원래 함수로 돌아올지도 명시적으로 결정할 수 있습니다.

간단한 코루틴 구현: yield 사용

yield는 함수 실행 중 일시 중단하고, 이후 다시 이어서 실행될 수 있도록 해줍니다. 이를 활용하면 함수 간의 전환이 가능해집니다.

import time

def dance():
    for i in range(5):
        print(f'춤을 추고 있습니다... {i}')
        yield
        time.sleep(0.1)

def sing():
    for i in range(5):
        print(f'노래를 하고 있습니다... {i}')
        yield
        time.sleep(0.1)

def run():
    dancer = dance()
    singer = sing()
    for _ in range(5):
        next(dancer)
        next(singer)
        time.sleep(0.1)

if __name__ == '__main__':
    run()

greenlet을 이용한 코루틴

greenletyield 기반의 코루틴을 보다 직관적으로 관리할 수 있도록 도와줍니다. 설치 후, 객체 생성 후 switch() 메서드로 실행을 전환할 수 있습니다.

import time
from greenlet import greenlet

def dance():
    for i in range(10):
        print(f'춤을 추고 있습니다... {i}')
        sing.switch()
        time.sleep(0.1)

def sing():
    for i in range(10):
        print(f'노래를 하고 있습니다... {i}')
        dance.switch()
        time.sleep(0.1)

dance_green = greenlet(dance)
sing_green = greenlet(sing)

def start():
    print('춤 시작')
    dance_green.switch()

if __name__ == '__main__':
    start()

gevent를 통한 자동 전환 코루틴

geventgreenlet을 기반으로 하되, 입출력(I/O) 작업(예: 네트워크 요청, 파일 읽기/쓰기 등)을 감지하면 자동으로 다른 코루틴으로 전환합니다. 이는 비동기 처리의 핵심입니다.

설치: pip install gevent

기본 사용법

  • time.sleep 같은 일반적인 지연은 gevent.sleep로 대체해야 비동기로 인식됨.
  • spawn로 코루틴 생성, join으로 완료 대기.
import gevent
import time

def dance(n):
    for i in range(n):
        print(f'춤을 추고 있습니다... {i}')
        gevent.sleep(0.5)

def sing(n):
    for i in range(n):
        print(f'노래를 하고 있습니다... {i}')
        gevent.sleep(0.5)

def main():
    print('=== 시작 ===')
    task1 = gevent.spawn(dance, 5)
    task2 = gevent.spawn(sing, 6)
    task1.join()
    task2.join()
    print('=== 종료 ===')

if __name__ == '__main__':
    main()

간결한 표현: monkey.patch_all()

monkey.patch_all()를 사용하면, 모든 블록킹 연산(예: time.sleep, socket 등)이 자동으로 gevent 호환 형태로 변경되어, 수동 교체 없이도 비동기 처리가 가능합니다.

import gevent
from gevent import monkey
monkey.patch_all()

def work(name, count):
    for i in range(count):
        print(f'{name}가 수행 중... {i}')
        time.sleep(0.2)

def main():
    t1 = gevent.spawn(work, '홍길동', 5)
    t2 = gevent.spawn(work, '김철수', 6)
    gevent.joinall([t1, t2])

if __name__ == '__main__':
    main()

태그: python coroutine gevent greenlet async

6월 14일 23:05에 게시됨