[Python] 코루틴 사용법

2024. 1. 26. 11:06

 

asyncio.run()

이 함수는 외부에서 호출되며,

코루틴을 실행하기 위한 새로운 이벤트 루프를 생성하고, 코루틴이 완료될 때까지 이벤트 루프를 실행한 후 종료

기본적으로 프로그램의 진입점에서 한 번 사용

asyncio.create_task()

이벤트 루프 내에서 비동기적으로 실행할 코루틴을 task로 스케쥴링

이렇게 생성된 task는 즉시 이벤트 루프에 의해 실행되며 task의 완료를 기다리지 않고 다음 줄의 코드가 실행됨

await

코루틴 실행을 일시 중단하고, 해당 코루틴이 완료될 때까지 현재 코루틴의 실행을 중지

완료되면, await 다음의 코드가 실행됨

 

비동기 작업이 완료될 때까지 기다려야 할 경우에 사용

ex) 특정 데이터를 받아와야 다음 단계의 코드를 실행할 수 있는 경우

 

await는 비동기 작업의 완료를 기다리지만, 전통적인 blocking 방식과는 다름

블록킹 방식에서는 작업이 완료될 때까지 프로그램의 실행히 완전히 멈춤

반면, 비동기 방식에서는 await으로 특정 작업의 완료를 기다리는 동안 이벤트 루프가 다른 비동기 작업을 수행할 수 있게 해줌

 

 

 

완료를 기다리지만 다른 작업을 수행할 수 있게 한다?

이해가 잘 안된다.

 

예시를 보자.

 

1. await을 만난 비동기 작업 A는 I/O 작업 등으로 인해 완료를 기다려야 한다.

2. 이벤트 루프는 작업 A가 완료될 때까지 대기하고 있을 동안, 다른 준비된 비동기 작업 B를 실행한다.

3. 작업 B도 await을 만나면, 이벤트 루프는 다시 다른 준비된 작업(ex: 작업 C)으로 전환할 수 있다.

4. 이 과정을 통해, 이벤트 루프는 실행 가능한 비동기 작업을 계속 찾아 실행하며, 각 작업의 대기 시간을 효율적으로 활용한다.

 

결국, await으로 인해 기다려야 하는 특정 작업이 있더라도, 프로그램 전체가 멈추는 것이 아니라,

가능한 다른 작업들을 계속해서 처리할 수 있다.

이것이 바로 비동기 프로그래밍이 제공하는 동시성의 이점이다!

 

 


 

import asyncio
import datetime

async def my_task(num, second):
    start_time = datetime.datetime.now()
    
    print(f"Task {num} 시작")
    await asyncio.sleep(second)  # second초 후에 작업이 완료됩니다.
    
    end_time = datetime.datetime.now()
    
    print(f"Task {num} 완료")
    
    return end_time - start_time

async def main():
    start_time = datetime.datetime.now()
    
    # my_task를 태스크로 스케줄링합니다. 
    # 태스크는 즉시 이벤트 루프에 의해 실행되지만, main()의 흐름은 여기서 멈추지 않습니다.
    task1 = asyncio.create_task(my_task(1, 2))

    # 이 시점에서 main()의 다음 줄로 바로 넘어갑니다. 태스크의 완료를 기다리지 않습니다.
    print("main()의 다음 코드 실행")
    
    task2 = asyncio.create_task(my_task(2, 5))

    # 그러나 여기서 await을 사용하여 태스크의 완료를 기다립니다.
    duration1 = await task1
    duration2 = await task2
    
    end_time = datetime.datetime.now()
    
    print(f"Task 1 소요 시간: {duration1}")
    print(f"Task 2 소요 시간: {duration2}")    
    print(f"main 함수의 전체 실행 시간: {end_time - start_time}")
    
# 이벤트 루프를 시작하고 main() 코루틴을 실행합니다.
asyncio.run(main())

 

전체 실행 시간이 7초가 아닌 5초로 단축됐다.

 


asyncio.Future()

비동기 작업의 결과를 나타내는데 사용되는 객체

아직 완료되지 않은 작업을 추적하고, 해당 작업이 완료되면 결과를 저장, 이후 결과 조회 가능

 

* Future 객체의 주요 메서드 *

 

cancel() : Future의 작업 취소, 작업이 이미 완료되었거나 취소되었다면 효과 없음

cancelled() : Future의 작업이 취소되었는지 여부 반환

done() : Future의 작업이 완료되었는지 여부 반환

result() : Future의 결과 반환, Future가 아직 완료되지 않았다면, 호출자를 block

set_result() : Future의 결과를 설정, Future가 완료되었음을 알리며, result() 호출에 의해 반환될 값 설정

 

asyncio.gather()

주어진 코루틴이나 Future 객체들을 동시에 실행하고, 모든 결과를 하나의 리스트로 반환

gather로 실행 중인 코루틴이라 Future 객체들이 독립적을 실행되고,

서로의 완료를 기다리지는 않음

 

import asyncio

async def worker(future):
    print('Worker: Starting work')
    await asyncio.sleep(1)
    print('Worker: Done with work')
    future.set_result('Worker result')

async def boss(future):
    print('Boss: Waiting for worker to finish')
    result = await future
    print(f'Boss: Received result: {result}')

async def main():
    future = asyncio.Future()
    await asyncio.gather(boss(future), worker(future))

asyncio.run(main())