본문 바로가기
대동단결 Python

파이썬을 위한 비동기 멀티테스킹 asyncio 사용하기

by 즐거운 지니 2024. 7. 19.
반응형

Python의 asyncio 라이브러리는 비동기 프로그래밍을 위해 제공되는 표준 라이브러리입니다. 비동기 프로그래밍은 주로 I/O 바운드 작업(예: 네트워크 요청, 파일 읽기/쓰기 등)에서 효율성을 높이기 위해 사용됩니다. asyncio를 사용하면 프로그램이 블로킹 없이 동시에 여러 작업을 수행할 수 있습니다.

기본 개념

  • 코루틴 (Coroutine): 비동기 함수로, async def로 정의됩니다. 코루틴은 await 키워드로 다른 코루틴이나 비동기 함수의 실행을 일시 중지하고 제어를 이벤트 루프로 반환할 수 있습니다.
  • 이벤트 루프 (Event Loop): 비동기 작업을 관리하고 실행하는 핵심 구성 요소입니다. 이벤트 루프는 등록된 모든 코루틴과 비동기 작업을 순차적으로 실행합니다.
  • 태스크 (Task): 이벤트 루프에서 실행되는 코루틴입니다. 태스크는 코루틴의 실행을 관리합니다.
  • 퓨처 (Future): 비동기 작업의 최종 결과를 나타내는 객체입니다.

기본 사용법

1. 코루틴 정의 및 실행

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

asyncio.run(say_hello())
  • async def로 코루틴을 정의합니다.
  • await를 사용하여 비동기 함수의 실행을 일시 중지합니다.
  • asyncio.run을 사용하여 코루틴을 실행합니다.

2. 여러 코루틴 실행

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

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

asyncio.run(main())
  • asyncio.gather를 사용하여 여러 코루틴을 동시에 실행할 수 있습니다.

3. 태스크 만들기

import asyncio

async def say_hello():
    print("Hello")
    await asyncio.sleep(1)
    print("World")

async def main():
    task1 = asyncio.create_task(say_hello())
    task2 = asyncio.create_task(say_hello())
    await task1
    await task2

asyncio.run(main())
  • asyncio.create_task를 사용하여 코루틴을 태스크로 만들어 실행합니다.

4. 비동기 I/O 작업

import asyncio

async def read_file():
    await asyncio.sleep(1)  # 파일을 읽는 대신 대기
    print("File read complete")

async def main():
    await read_file()

asyncio.run(main())

실용 예제

비동기 TCP 서버

import asyncio

async def handle_client(reader, writer):
    addr = writer.get_extra_info('peername')
    print(f"Connected to {addr}")

    while True:
        data = await reader.read(100)
        if not data:
            break
        message = data.decode()
        print(f"Received {message} from {addr}")
        writer.write(data)
        await writer.drain()
    print(f"Disconnected from {addr}")
    writer.close()
    await writer.wait_closed()

async def main():
    server = await asyncio.start_server(handle_client, '127.0.0.1', 8888)
    async with server:
        await server.serve_forever()

asyncio.run(main())

비동기 TCP 클라이언트

import asyncio

async def tcp_client():
    reader, writer = await asyncio.open_connection('127.0.0.1', 8888)

    message = 'Hello, World!'
    print(f'Send: {message}')
    writer.write(message.encode())
    await writer.drain()

    data = await reader.read(100)
    print(f'Received: {data.decode()}')

    print('Close the connection')
    writer.close()
    await writer.wait_closed()

asyncio.run(tcp_client())

주요 함수 및 메서드

  • asyncio.run(coroutine): 이벤트 루프를 시작하고 주어진 코루틴을 실행합니다.
  • asyncio.create_task(coroutine): 코루틴을 태스크로 만들어 이벤트 루프에서 실행합니다.
  • asyncio.gather(*coroutines): 여러 코루틴을 동시에 실행하고 결과를 수집합니다.
  • asyncio.sleep(seconds): 주어진 시간 동안 비동기적으로 대기합니다.
  • asyncio.start_server(): 비동기 TCP 서버를 시작합니다.
  • asyncio.open_connection(): 비동기 TCP 클라이언트를 시작합니다.

asyncio는 효율적인 비동기 프로그래밍을 위한 강력한 도구입니다. 이를 사용하여 네트워크 서버, 클라이언트, 파일 I/O 등을 비동기적으로 처리할 수 있습니다.

asyncio.gatherasyncio.create_task는 모두 여러 코루틴을 동시에 실행하기 위한 방법이지만, 두 방법에는 중요한 차이점이 있습니다.

asyncio.gather

asyncio.gather는 주어진 코루틴들을 동시에 실행하고, 모든 코루틴이 완료되면 결과를 반환합니다. asyncio.gather를 사용하면 각 코루틴의 결과를 모아서 반환하기 때문에, 각 코루틴의 결과를 다루기 쉽습니다.

import asyncio

async def task1():
    await asyncio.sleep(1)
    return 'Task 1 completed'

async def task2():
    await asyncio.sleep(2)
    return 'Task 2 completed'

async def main():
    results = await asyncio.gather(task1(), task2())
    print(results)

asyncio.run(main())

위 예제에서 task1task2는 동시에 실행됩니다. asyncio.gather는 두 코루틴이 모두 완료될 때까지 기다렸다가 결과 리스트를 반환합니다.

asyncio.create_task

asyncio.create_task는 코루틴을 Task 객체로 감싸서 바로 실행을 시작합니다. create_task는 즉시 실행을 시작하지만, 결과를 얻으려면 태스크를 await해야 합니다.

import asyncio

async def task1():
    await asyncio.sleep(1)
    return 'Task 1 completed'

async def task2():
    await asyncio.sleep(2)
    return 'Task 2 completed'

async def main():
    t1 = asyncio.create_task(task1())
    t2 = asyncio.create_task(task2())
    await t1
    await t2
    print(t1.result(), t2.result())

asyncio.run(main())

위 예제에서 task1task2create_task를 통해 태스크로 만들어져 즉시 실행됩니다. await 키워드를 사용하여 각각의 태스크가 완료될 때까지 기다린 후 결과를 얻습니다.

차이점 요약

  1. 결과 관리:
    • asyncio.gather는 모든 코루틴의 결과를 한 번에 반환하므로, 결과를 관리하기 쉽습니다.
    • asyncio.create_task는 각 태스크의 결과를 개별적으로 관리해야 합니다.
  2. 예외 처리:
    • asyncio.gather는 실행된 모든 코루틴 중 하나라도 예외가 발생하면, 나머지 코루틴들이 완료되기 전에 예외를 발생시킵니다.
    • asyncio.create_task를 사용하면 개별 태스크에서 예외가 발생할 수 있으며, 각 태스크에서 발생한 예외를 개별적으로 처리할 수 있습니다.
  3. 제어권:
    • asyncio.gather는 모든 코루틴이 완료될 때까지 기다립니다.
    • asyncio.create_task는 태스크를 실행 상태로 만들고, 나중에 원하는 시점에 await할 수 있습니다. 즉, 더 많은 제어권을 제공합니다.

예제: 예외 처리 비교

asyncio.gather 예외 처리

import asyncio

async def task1():
    await asyncio.sleep(1)
    return 'Task 1 completed'

async def task2():
    await asyncio.sleep(2)
    raise ValueError('An error occurred in task2')

async def main():
    try:
        results = await asyncio.gather(task1(), task2())
    except Exception as e:
        print(f'Exception caught: {e}')

asyncio.run(main())

asyncio.create_task 예외 처리

import asyncio

async def task1():
    await asyncio.sleep(1)
    return 'Task 1 completed'

async def task2():
    await asyncio.sleep(2)
    raise ValueError('An error occurred in task2')

async def main():
    t1 = asyncio.create_task(task1())
    t2 = asyncio.create_task(task2())
    await t1
    try:
        await t2
    except Exception as e:
        print(f'Exception caught: {e}')

asyncio.run(main())

위 예제에서 asyncio.gathertask2에서 예외가 발생하면 즉시 예외를 발생시킵니다. 반면, asyncio.create_task를 사용하면 개별적으로 예외를 처리할 수 있습니다.

결론

  • asyncio.gather는 여러 코루틴의 결과를 한 번에 처리하고 싶을 때 유용합니다.
  • asyncio.create_task는 더 많은 제어권과 유연성을 제공하며, 개별 태스크를 관리하고 예외를 개별적으로 처리할 때 유용합니다.

이 두 가지 방법을 상황에 맞게 사용하면 비동기 프로그래밍을 더 효과적으로 관리할 수 있습니다.

반응형

댓글