Python - asyncio로 비동기 처리 구현

파이썬에서 asyncio 라이브러리로 코루틴(coroutine) 개념을 사용할 수 있으며, 코루틴으로 어떤 작업을 비동기로 처리할 수 있습니다.

코루틴은 light weight thread라고 불리며, 파이썬 뿐만 아니라 다른 프로그래밍 언어에서도 사용되는 개념입니다. 프로그램이 싱글 쓰레드로 동작해도, 코루틴(co-routine)으로 멀티 쓰레드에서 동작하는 것처럼 비동기 프로그래밍을 할 수 있습니다.

1. 비동기로 함수 호출

  • 코루틴 함수를 정의하려면 함수 앞에 async 키워드를 붙여야 합니다.
  • 코루틴 함수 안에서 코루틴 함수를 실행할 때 함수 이름 앞에 await을 붙여야 합니다.
  • 코루틴 함수는 loop에서만 동작하며 asyncio.get_event_loop()로 loop를 얻을 수 있습니다.
  • 코루틴 함수를 실행할 때 run_until_complete()의 인자로 전달하여 실행시킬 수 있습니다. 이 함수는 실행시킨 코루틴이 완료될 때까지 대기합니다.

아래와 같이 mul()이라는 코루틴을 만들고, main에서 코루틴을 실행시킬 수 있습니다. main 쓰레드는 mul()을 비동기로 실행시키며, 이 함수가 완료될 때까지 기다렸다 결과를 리턴받습니다.

import asyncio

async def mul(x, y):
    return x + y


if __name__ == "__main__":
    loop = asyncio.get_event_loop()

    res = loop.run_until_complete(mul(5, 5))

    print(res)
    loop.close()

Output:

10

위 코드에서는 run_until_complete()에 코루틴을 직접 전달하였지만, 아래와 같이 create_task()로 task를 생성하고 run_until_complete()에 전달해도 됩니다.

task = loop.create_task(mul(10, 10))

res = loop.run_until_complete(task)

2. 순차적인 비동기 함수 호출

main 쓰레드와 별개로, 다른 쓰레드에서 동작하는 것을 비동기라고 할 수 있습니다.

순차적인 비동기라고 표현한 것은, 비동기 작업 3개를 순차적으로 실행한다는 의미입니다. 즉, 첫번째 비동기 작업이 끝나고 두번째 비동기 작업 실행, 두번째 비동기 작업이 끝나고 세번째 비동기 작업을 실행합니다.

아래와 같이 run_until_complete()를 사용하여 비동기 작업이 완료될 때까지 대기하고 다음 비동기 작업이 실행되도록 하였습니다.

import asyncio
import time

async def task(tid, n):
    print(f'Task {tid} started')
    await asyncio.sleep(n)
    print(f'Task {tid} finished')


if __name__ == "__main__":
    loop = asyncio.get_event_loop()

    start = time.perf_counter()

    t1 = loop.create_task(task(1, 3))
    loop.run_until_complete(t1)
    print(f"Took: {time.perf_counter() - start:0.2f} seconds")

    t2 = loop.create_task(task(2, 2))
    loop.run_until_complete(t2)
    print(f"Took: {time.perf_counter() - start:0.2f} seconds")

    t3 = loop.create_task(task(3, 1))
    loop.run_until_complete(t3)
    print(f"Took: {time.perf_counter() - start:0.2f} seconds")

    loop.close()

Output:

Task 1 started
Task 1 finished
Took: 3.00 seconds
Task 2 started
Task 2 finished
Took: 5.00 seconds
Task 3 started
Task 3 finished
Took: 6.01 seconds

3. 다수의 비동기 작업 동시에 실행

위의 예제는 3개의 비동기 작업을 순차적으로 실행해서 6초나 걸렸습니다. 동시에 실행했다면 3초면 끝날 작업이었습니다.

asyncio.gather()는 여러 비동기 작업을 동시에 실행하여 결과를 기다릴 때 사용할 수 있습니다.

아래와 같이 3개의 작업을 동시에 실행하고, 모두 끝날 때까지 기다릴 수 있습니다.

import asyncio
import time


async def task(tid, n):
    print(f'Task {tid} started')
    await asyncio.sleep(n)
    print(f'Task {tid} finished')

if __name__ == "__main__":
    loop = asyncio.get_event_loop()

    start = time.perf_counter()

    tasks = [
        loop.create_task(task(1, 3)),
        loop.create_task(task(2, 2)),
        loop.create_task(task(3, 1))
    ]

    loop.run_until_complete(asyncio.gather(*tasks))
    print(f"Took: {time.perf_counter() - start:0.2f} seconds")

    loop.close()

Output:

Task 1 started
Task 2 started
Task 3 started
Task 3 finished
Task 2 finished
Task 1 finished
Took: 3.00 seconds

asyncio.gather(*tasks)처럼, 리스트로 task를 구성하지 않았다면, 아래와 같이 task들을 직접 입력할 수도 있습니다.

task1 = loop.create_task(task(1, 3))
task2 = loop.create_task(task(2, 2))
task3 = loop.create_task(task(3, 1))

loop.run_until_complete(asyncio.gather(task1, task2, task3))

4. asyncio.run()로 간단히 비동기 작업 실행

위의 예제에서는 loop를 가져오고 loop를 통해서 코루틴을 실행시켰습니다.

asyncio.run()은 인자로 전달된 코루틴을 loop에서 실행시키고 완료될 때까지 대기합니다. asyncio의 기본 loop를 사용한다면 asyncio.run()로 간단히 비동기 처리를 구현할 수 있습니다.

import asyncio
import time

async def task(tid, n):
    print(f'Task {tid} started')
    await asyncio.sleep(n)
    print(f'Task {tid} finished')

async def my_tasks():
    t1 = asyncio.create_task(task(1, 3))
    t2 = asyncio.create_task(task(2, 2))
    t3 = asyncio.create_task(task(3, 1))
    await asyncio.gather(t1, t2, t3)


if __name__ == "__main__":
    start = time.perf_counter()

    asyncio.run(my_tasks())
    print(f"Took: {time.perf_counter() - start:0.2f} seconds")

Output:

Task 1 started
Task 2 started
Task 3 started
Task 3 finished
Task 2 finished
Task 1 finished
Took: 3.00 seconds

5. 코루틴 함수에서 다른 코루틴 함수 호출

코루틴 함수에서 다른 코루틴 함수를 호출하려면 await 키워드를 사용해야 합니다.

아래 예제에서 task()가 코루틴 함수인 do_something()를 호출할 때 await do_something()처럼, await 키워드를 붙여줘야 합니다.

import asyncio
import time

async def do_something():
    print('do something...')
    await asyncio.sleep(1)

async def task(tid, n):
    print(f'Task {tid} started')
    await do_something()
    await asyncio.sleep(n)
    print(f'Task {tid} finished')

async def my_tasks():
    t1 = asyncio.create_task(task(1, 3))
    t2 = asyncio.create_task(task(2, 2))
    t3 = asyncio.create_task(task(3, 1))
    await asyncio.gather(t1, t2, t3)

if __name__ == "__main__":
    start = time.perf_counter()

    asyncio.run(my_tasks())
    print(f"Took: {time.perf_counter() - start:0.2f} seconds")

Output:

Task 1 started
do something...
Task 2 started
do something...
Task 3 started
do something...
Task 3 finished
Task 2 finished
Task 1 finished
Took: 4.00 seconds
Loading script...

Related Posts

codechachaCopyright ©2019 codechacha