Async

Published by onesixx on

https://docs.python.org/ko/3/glossary.html#term-coroutine, PEP 492์ฐธ์กฐ

https://docs.python.org/ko/3/library/asyncio-task.html#coroutines

https://brownbears.tistory.com/490ย 

coroutine (์ฝ”๋ฃจํ‹ด)

Sub-routine์˜ ๋” ์ผ๋ฐ˜ํ™”๋œ ํ˜•ํƒœ
asyncio ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜๋Š” ๊ธฐ๋ณธ ๋ฐฉ๋ฒ•
๋™์‹œ์„ฑ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ๊ฐ€๋Šฅ์ผ€ ํ•˜๋„๋ก ๋งŒ๋“  ๊ฐœ๋…

์ด๊ฒƒ๋“ค์€ async def ๋ฌธ์œผ๋กœ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 

sub-routine vs. co-routine

  • sub-routine์€ ํ•œ ์ง€์ ์—์„œ ์ง„์ž…ํ•˜๊ณ  ๋‹ค๋ฅธ ์ง€์ ์—์„œ ํƒˆ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • co-routine์€ ์—ฌ๋Ÿฌ ๋‹ค๋ฅธ ์ง€์ ์—์„œ ์ง„์ž…ํ•˜๊ณ , ํƒˆ์ถœํ•˜๊ณ , ์žฌ๊ฐœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Thread vs. co-routine

coroutine์€ ๋‚˜์˜จ์ง€ ๊ฝค๋‚˜ ์˜ค๋ž˜๋œ ๊ธฐ์ˆ ์ด๋‚˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๋‚˜ํƒ€๋‚˜๋ฉด์„œ ๋ฐฉ์น˜๊ฐ€ ๋˜์—ˆ์—ˆ์Šต๋‹ˆ๋‹ค.
์ดํ›„ ์Šค๋ ˆ๋“œ์˜ ์ž์› ๊ฒฝ์Ÿ, ๋ฐ๋“œ๋ฝ ๋“ฑ์˜ ๋ฌธ์ œ๋กœ ์ธํ•ด ๋‹ค์‹œ ์ฃผ๋ชฉ๋ฐ›๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค
์ฝ”๋ฃจํ‹ด์€ ๋ณ„๋„์˜ Thread์—†์ด ๋ฉ”์ธ Thread์ƒ์—์„œ ๋ฒˆ๊ฐˆ์•„๊ฐ€๋ฉฐ, ๋ณ‘๋ ฌ์ฒ˜๋ฆฌ์™€ ์œ ์‚ฌํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ.

์Šค๋ ˆ๋“œ๋Š” ๋น„๋™๊ธฐ๋กœ, ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ์žˆ๋‹ค๋ฉด ํ•œ๊บผ๋ฒˆ์— ๋™์‹œ์— ์‹คํ–‰๋œ๋‹ค.

์ฝ”๋ฃจํ‹ด์€ ํ”„๋กœ๊ทธ๋žจ์ด ์‹คํ–‰ ์ค‘์ผ ๋•Œ, ํŠน์ • ์‹œ์ ์— ์ฝ”๋ฃจํ‹ด์œผ๋กœ ์ด๋™ํ•˜๊ณ  ๊ทธ ์ „์— ์ง„ํ–‰ ์ค‘์ด๋˜ ๋ฃจํ‹ด์€ ์ •์ง€ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ํ•œ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋งŒ ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๊ธฐ์กด์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ ์œ ์‚ฌํ•œ ์„ฑ๊ฒฉ์œผ๋กœ ๋ณด์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ํ•˜์ง€๋งŒ ๊ธฐ์กด์˜ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ์—๋Ÿฌ๊ฐ€ ๋‚˜์ง€ ์•Š๋Š” ์ด์ƒ ์‹คํ–‰ ์ค‘์ธ ์ฝ”๋“œ๋ฅผ ๋น ์ ธ๋‚˜์˜ฌ ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์€ return๊ณผ ๊ฐ™์ด ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์ด์ง€๋งŒ ์ฝ”๋ฃจํ‹ด์€ ์‹คํ–‰ ์ค‘๊ฐ„์— ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ๋น ์ ธ ๋‚˜์™€ ๋‹ค๋ฅธ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ณ  ๋‹ค์‹œ ์‹คํ–‰ ์ค‘์ด์˜€๋˜ ์ฝ”๋“œ ๋ผ์ธ์œผ๋กœ ์ด๋™ํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
๋‹ค์‹œ ๋งํ•ด์„œย ์ฝ”๋ฃจํ‹ด์€ yield ํ‚ค์›Œ๋“œ๋กœ ์‹คํ–‰์„ ์ค‘์ง€ํ•  ์ˆ˜ ์žˆ๊ณ , yield ํ‚ค์›Œ๋“œ๋ฅผ ํ˜ธ์ถœํ–ˆ๋˜ ๊ณณ์œผ๋กœ ์™€์„œ ์‹คํ–‰์„ ์žฌ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์‹ฑ๊ธ€์ฝ”์–ด์—์„  ์ฝ”๋ฃจํ‹ด์€ ์ด๋™ ์‹œ์ ์ด ๋” ์ž˜ ์กฐ์ ˆ๋˜๊ณ  context switching์ด ์ ์–ด, ์„ฑ๋Šฅ๋ฉด์—์„œ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ณด๋‹ค ์ข‹์„ ์ˆ˜ ์žˆ์ง€๋งŒย ๋ฉ€ํ‹ฐ์ฝ”์–ด ํ”„๋กœ์„ธ์„œ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์—†๋‹ค๋Š” ๋ฌธ์ œ์ ์ด ์žˆ์œผ๋ฉฐ ์Šค๋ ˆ๋“œ๋ณด๋‹ค ์„ฑ๋Šฅ์ด ๋–จ์–ด์ง€๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

coroutine function (์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜)

co-routine๊ฐ์ฒด๋ฅผ ๋Œ๋ ค์ฃผ๋Š” ํ•จ์ˆ˜.

์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜๋Š”ย asyncย defย ๋ฌธ์œผ๋กœ ์ •์˜๋  ์ˆ˜ ์žˆ๊ณ ,
awaitย ์™€ย asyncย for์™€ย asyncย withย ํ‚ค์›Œ๋“œ๋ฅผ ํฌํ•จํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

asyncio ์‘์šฉ ํ”„๋กœ๊ทธ๋žจ์„ ์ž‘์„ฑํ•˜๋Š” ๊ธฐ๋ณธ ๋ฐฉ๋ฒ•

async/await ๋ฌธ๋ฒ•์œผ๋กœ ์„ ์–ธ๋œย co-routine

ex) ใ€Šhelloใ€‹๋ฅผ ์ธ์‡„ํ•˜๊ณ , 1์ดˆ ๋™์•ˆ ๊ธฐ๋‹ค๋ฆฐ ๋‹ค์Œ, ใ€Šworldใ€‹๋ฅผ ์ธ์‡„

import asyncio


async def main():
    print('hello')
    await asyncio.sleep(1)
    print('world')

asyncio.run(
    main()
)
>>>asyncio.run( main() )
RuntimeError: RuntimeError: asyncio.run() cannot be called from a running event loop
 
>>> main()


>>> await main()

ํ•จ์ˆ˜๋Š” ํ˜ธ์ถœ๋ ์ˆ˜ ์—†๋‹ค. ๋‹ค๋ฅธ asyncio ์ด๋ฒคํŠธ๋ฃจํ”„๊ฐ€ ๊ฐ™์€ thread์— runningํ•˜๊ณ  ์žˆ๋Š”๊ฒฝ์šฐ.

์•„๋งˆ๋„, Jupyter์—์„œ ์ด๋ฒคํŠธ๋ฃจํ”„๊ฐ€ running์ค‘.

In your case, jupyter (IPython โ‰ฅ 7.0) is already running an event loop:

์œ„ ์˜ˆ์ œ๋Š” ํƒœ์Šคํฌ๊ฐ€ 1๊ฐœ์ด๋ฏ€๋กœ ๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ๊ณผ ํฌ๊ฒŒ ๋‹ค๋ฅด์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
์•„๋ž˜๋Š” 2๊ฐœ์˜ ํƒœ์Šคํฌ๋ฅผ ๋™๊ธฐ์™€ ๋น„๋™๊ธฐ๋กœ ์‹คํ–‰ํ•œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

[Python] asyncio ํŒŒํ—ค์น˜๊ธฐ (python 3.8๊ธฐ์ค€)

asyncio๋ž€?

https://docs.python.org/ko/3.8/library/asyncio.html

๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ์œ„ํ•œ ๋ชจ๋“ˆ

ํŒŒ์ด์ฌ 3.5๋ถ€ํ„ฐ ์ง€์›

๋™๊ธฐ๋ž€ ๋นจ๋ž˜๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ์ข…๋ฃŒ๊ฐ€ ๋˜๋ฉด ์„ค๊ฑฐ์ง€๋ฅผ ์‹œ์ž‘ํ•˜๊ณ  ์™„๋ฃŒ๊ฐ€ ๋˜๋ฉด TV๋ฅผ ๋ณด๋Š” ๊ฒƒ์ฒ˜๋Ÿผ ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ž‘์—…์„ ํ•˜๋Š” ๊ฒƒ์ด๊ณ ,

๋น„๋™๊ธฐ๋Š” ๋นจ๋ž˜๋ฅผ ์‹œ์ž‘์‹œํ‚ค๊ณ  ์„ค๊ฑฐ์ง€๋ฅผ ํ•˜๋ฉด์„œ TV๋ฅผ ๋ณด๋Š” ๊ฒƒ๊ณผ ๊ฐ™์ด ์—ฌ๋Ÿฌ ์ž‘์—…์„ ๋™์‹œ์— ํ•˜๋Š” ๊ฒƒ๊ณผ ๊ฐ™์€ ํ–‰๋™์„ ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

ํ•˜์ง€๋งŒ ํŒŒ์ด์ฌ์—์„œ๋Š” GIL(Global interpreter Lock) ๋•Œ๋ฌธ์— ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์ด ๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ๋ณด๋‹ค ๋А๋ฆด ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

asyncio๋Š” ์ด๋ฒคํŠธ ๋ฃจํ”„์™€ ์ฝ”๋ฃจํ‹ด์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋™์ž‘ํ•˜๋ฉฐ

๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๊ณ  ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” I/O boundํ•œ ์ž‘์—…์—์„œ ํšจ์œจ์ ์ž…๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜์ด๋ฏ€๋กœ ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ์™€ ๋น„๊ตํ•˜์—ฌ ๋ฌธ๋งฅ๊ตํ™˜์— ๋”ฐ๋ฅธ ๋น„์šฉ์ด ๋‹ค์†Œ ์ ๊ฒŒ ๋“ค์–ด๊ฐ‘๋‹ˆ๋‹ค.

์ด๋ฒคํŠธ ๋ฃจํ”„๋ž€?

์ด๋ฒคํŠธ ๋ฃจํ”„๋Š” ์ž‘์—…๋“ค์„ ๋ฐ˜๋ณต๋ฌธ ๋Œ๋ฉด์„œ ํ•˜๋‚˜์”ฉ ์‹คํ–‰์„ ์‹œํ‚ต๋‹ˆ๋‹ค.

์ด๋•Œ, ์‹คํ–‰ํ•œ ์ž‘์—…์ด ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๊ณ  ์‘๋‹ต์„ ๊ธฐ๋‹ค๋ฆฐ๋‹ค๋ฉด ๋‹ค๋ฅธ ์ž‘์—…์—๊ฒŒ event loop์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ ๋„˜๊น๋‹ˆ๋‹ค.
๊ถŒํ•œ์„ ๋ฐ›์€ event loop๋Š” ๋‹ค์Œ ์ž‘์—…์„ ์‹คํ–‰ํ•˜๊ณ  ์‘๋‹ต์„ ๋ฐ›์€ ์ˆœ์„œ๋Œ€๋กœ ๋Œ€๊ธฐํ•˜๋˜ ์ž‘์—…๋ถ€ํ„ฐ ๋‹ค์‹œ ๊ถŒํ•œ์„ ๊ฐ€์ ธ์™€ ์ž‘์—…์„ ๋งˆ๋ฌด๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๊ณต์‹ ๋ฌธ์„œ: https://docs.python.org/ko/3.8/library/asyncio-eventloop.html

asyncio

์ฝ”๋ฃจํ‹ด์œผ๋กœ ํƒœ์Šคํฌ๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค๋ฉด,ย 
asyncio.get_event_loopํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•ด ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ •์˜ํ•˜๊ณ 
run_until_complete์œผ๋กœ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•จ์ˆ˜ ์•ž์— async๋ฅผ ๋ถ™์ด๋ฉด, asyncio๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๊ณ , ์ฝ”๋ฃจํ‹ด์ด ๋งŒ๋“ค์–ด์ง.
I/O๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜ ๊ถŒํ•œ์„ ๋‹ค๋ฅธ ์ž‘์—…์— ๋„˜๊ธฐ๊ณ  ์‹ถ์„ ๋•Œ์—”, ํ•ด๋‹น ๋กœ์ง ์•ž์— await์„ ๋ถ™์ž…๋‹ˆ๋‹ค.

์ด๋•Œ await ๋’ค์— ์˜ค๋Š” ์ฝ”๋“œ๋Š” ์ฝ”๋ฃจํ‹ด์œผ๋กœ ์ž‘์„ฑ๋œ ์ฝ”๋“œ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋งŒ์•ฝ await ๋’ค์— time.sleep๊ณผ ๊ฐ™์ด ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์Šค๋ ˆ๋“œ๊ฐ€ ์ค‘๋‹จ๋˜๋ฏ€๋กœ ์˜๋„ํ•œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
๋”ฐ๋ผ์„œ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ๋Š” asyncio.sleep์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
๋ฐ‘์—์„œ ์„ค๋ช…ํ•˜๊ฒ ์ง€๋งŒ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ๋ชจ๋“ˆ์ด ์•„๋‹ˆ๋ผ๋ฉด (requests, psycopg, django orm ๋“ฑ๋“ฑ) await์„ ๋ถ™์—ฌ๋„ ์†Œ์šฉ์ด ์—†์Šต๋‹ˆ๋‹ค.

๋™๊ธฐ

๋น„๋™๊ธฐ

import asyncio
import time

async def async_task_1():
    print('async_task_1 ์‹œ์ž‘')
    print('sync_task_1 3์ดˆ ๋Œ€๊ธฐ')
    await asyncio.sleep(3)
    print('sync_task_1 ์žฌ์‹œ์ž‘')

async def async_task_2():
    print('async_task_2 ์‹œ์ž‘')
    print('sync_task_2 2์ดˆ ๋Œ€๊ธฐ')
    await asyncio.sleep(2)
    print('sync_task_2 ์žฌ์‹œ์ž‘')

async def main():
    start = time.time()
    await async_task_1()
    await async_task_2()
    end = time.time()
    print(f'time taken: {end - start}')

asyncio.run(main())

# async_task_1 ์‹œ์ž‘
# sync_task_1 3์ดˆ ๋Œ€๊ธฐ
# sync_task_1 ์žฌ์‹œ์ž‘
# async_task_2 ์‹œ์ž‘
# sync_task_2 2์ดˆ ๋Œ€๊ธฐ
# sync_task_2 ์žฌ์‹œ์ž‘
# time taken: 5.004269123077393

asyncio๋ฅผ ์“ฐ๋ฉด ๋” ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ ์‹œ๊ฐ„์ด ๋™์ผํ•˜๊ฒŒ ๊ฑธ๋ ธ์Šต๋‹ˆ๋‹ค. ์œ„์™€ ๊ฐ™์ด ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜ ์—ฌ๋Ÿฌ๊ฐœ๋ฅผ ํ•œ๋ฒˆ์— ์‹คํ–‰ํ•ด์•ผ ํ•˜๋Š”๋ฐ await ์ฝ”๋ฃจํ‹ดํ•จ์ˆ˜() ์ด๋Ÿฐ ์‹์œผ๋กœ ๋‚˜์—ดํ•˜๋ฉด ์ฝ”๋ฃจํ‹ด์„ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด์ง€ ๋‹ค์Œ ํƒœ์Šคํฌ๋ฅผ ์‹คํ–‰ํ•˜๋„๋ก ์˜ˆ์•ฝํ•˜๋Š” ํ–‰๋™์€ ์•„๋‹™๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ์ฝ”๋ฃจํ‹ด์„ ๋™์‹œ์— ์‹คํ–‰ํ•˜์—ฌ ์›ํ•˜๋Š” ๋™์ž‘์„ํ•˜๊ธฐ ์œ„ํ•ด์„  ์•„๋ž˜์™€ ๊ฐ™์ด create_task()๋กœ ๋“ฑ๋กํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

import asyncio
import time

async def async_task_1():
    print('async_task_1 ์‹œ์ž‘')
    print('sync_task_1 3์ดˆ ๋Œ€๊ธฐ')
    await asyncio.sleep(3)
    print('sync_task_1 ์žฌ์‹œ์ž‘')

async def async_task_2():
    print('async_task_2 ์‹œ์ž‘')
    print('sync_task_2 2์ดˆ ๋Œ€๊ธฐ')
    await asyncio.sleep(2)
    print('sync_task_2 ์žฌ์‹œ์ž‘')

async def main():
    start = time.time()
    task1 = asyncio.create_task(async_task_1())
    task2 = asyncio.create_task(async_task_2())
    await task1
    await task2
    end = time.time()
    print(f'time taken: {end - start}')

asyncio.run(main())

# async_task_1 ์‹œ์ž‘
# sync_task_1 3์ดˆ ๋Œ€๊ธฐ
# async_task_2 ์‹œ์ž‘
# sync_task_2 2์ดˆ ๋Œ€๊ธฐ
# sync_task_2 ์žฌ์‹œ์ž‘
# sync_task_1 ์žฌ์‹œ์ž‘
# time taken: 3.0023632049560547

ํ˜„์žฌ ํŒŒ์ด์ฌ 3.8์„ ์‚ฌ์šฉํ•ด์„œ ์†์‰ฝ๊ฒŒ asyncio.run(๋ฉ”์ธํ•จ์ˆ˜())๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์ดํ•˜ ๋ฒ„์ „์—์„œ๋Š” ์ด๋ฒคํŠธ ๋ฃจํ”„์—์„œ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ๊ฐ€์ ธ์˜จ ๋‹ค์Œ ์‹คํ–‰์‹œ์ผœ์•ผ ํ•ฉ๋‹ˆ๋‹ค. asyncio.run() ํ•จ์ˆ˜ ๋‚ด๋ถ€๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

def run(main, *, debug=None):
    """Execute the coroutine and return the result.

    This function runs the passed coroutine, taking care of
    managing the asyncio event loop and finalizing asynchronous
    generators.

    This function cannot be called when another asyncio event loop is
    running in the same thread.

    If debug is True, the event loop will be run in debug mode.

    This function always creates a new event loop and closes it at the end.
    It should be used as a main entry point for asyncio programs, and should
    ideally only be called once.

    Example:

        async def main():
            await asyncio.sleep(1)
            print('hello')

        asyncio.run(main())
    """
    if events._get_running_loop() is not None:
        raise RuntimeError(
            "asyncio.run() cannot be called from a running event loop")

    if not coroutines.iscoroutine(main):
        raise ValueError("a coroutine was expected, got {!r}".format(main))

    loop = events.new_event_loop()
    try:
        events.set_event_loop(loop)
        if debug is not None:
            loop.set_debug(debug)
        return loop.run_until_complete(main)
    finally:
        try:
            _cancel_all_tasks(loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
        finally:
            events.set_event_loop(None)
            loop.close()

๋‹ค๋ฅธ asyncio ์ด๋ฒคํŠธ ๋ฃจํ”„๊ฐ€ ๋™์ผํ•œ ์Šค๋ ˆ๋“œ์—์„œ ์‹คํ–‰ ์ค‘์ผ ๋•Œ, asyncio.run() ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ•ญ์ƒ ์ƒˆ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ๋งŒ๋“ค๊ณ  ๋‹ค ์‚ฌ์šฉํ•œ ๋‹ค์Œ, ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ข…๋ฃŒ ์‹œํ‚ต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ asyncio ํ”„๋กœ๊ทธ๋žจ์„ ์งค ๋•Œ, ํ•ด๋‹น ํ•จ์ˆ˜๋Š” ๋ฉ”์ธ ์ง„์ž… ์ง€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ  1๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋™์‹œ์— ์‹คํ–‰ํ•˜๊ธฐ

์œ„ ์˜ˆ์ œ ์ค‘, asyncio.create_task()๋กœ ๊ฐ awaitable ํ•จ์ˆ˜๋ฅผ ๋“ฑ๋กํ•ด์„œ ํ•˜๋Š” ๋ฐฉ๋ฒ•๋„ ์žˆ์ง€๋งŒ ๋“ฑ๋กํ•ด์•ผ ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ๋งŽ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด asyncio.gather()๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")
    return f

async def main():
    # ๋™์‹œ์— 3๊ฐœ๋ฅผ ์˜ˆ์•ฝ
    result = await asyncio.gather(factorial("A", 2), factorial("B", 3), factorial("C", 4))
    # ๋˜๋Š”
    # result = await asyncio.gather(*[factorial("A", 2), factorial("B", 3), factorial("C", 4)])

    print(result)

asyncio.run(main())

# Task A: Compute factorial(2)...
# Task B: Compute factorial(2)...
# Task C: Compute factorial(2)...
# Task A: factorial(2) = 2
# Task B: Compute factorial(3)...
# Task C: Compute factorial(3)...
# Task B: factorial(3) = 6
# Task C: Compute factorial(4)...
# Task C: factorial(4) = 24
# [2, 6, 24]

๋“ฑ๋ก๋œ ๋ชจ๋“  ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜๊ฐ€ ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด ๋“ฑ๋ก๋œ ์ˆœ์„œ๋Œ€๋กœ ๊ฒฐ๊ณผ๊ฐ’์ด ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. asyncio.gather() ํ•จ์ˆ˜์˜ ์„ธ๋ฒˆ์งธ ํŒŒ๋ผ๋ฏธํ„ฐ๋Š” return_exceptions๋กœ ๊ธฐ๋ณธ๊ฐ’์€ False์ด๋ฉฐ ๋“ฑ๋ก๋œ ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜ ์ค‘ 1๊ฐœ๋ผ๋„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ฆ‰์‹œ ์ค‘๋‹จ์‹œํ‚ค๊ณ  ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค. True๋กœ ์„ค์ •ํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋‚œ ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜๋Š” ์‹คํ–‰๋˜์ง€ ์•Š๊ณ  ๊ฒฐ๊ณผ ๋ฆฌ์ŠคํŠธ์— ์—๋Ÿฌ ์ •๋ณด๋ฅผ ๋‹ด์•„์„œ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

import asyncio

async def factorial(name, number):
    f = 1
    if number == 4:
        raise ValueError('์—๋Ÿฌ')
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")
    return f

async def main():
    # ๋™์‹œ์— 3๊ฐœ๋ฅผ ์˜ˆ์•ฝ
    result = await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
        return_exceptions=True
    )

    print(result)

asyncio.run(main())

# Task A: Compute factorial(2)...
# Task B: Compute factorial(2)...
# Task A: factorial(2) = 2
# Task B: Compute factorial(3)...
# Task B: factorial(3) = 6
# [2, 6, ValueError('์—๋Ÿฌ')]

์‹œ๊ฐ„์ œํ•œ๋‘๊ธฐ

๋งŒ์•ฝ ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜์— ์‹œ๊ฐ„์ œํ•œ์„ ๋‘๊ณ  ์‹ถ๋‹ค๋ฉด asyncio.wait_for() ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

import asyncio

async def eternity():
    await asyncio.sleep(4)
    print('์‚ด์•˜๋‹ค.')

async def main():
    try:
        # ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด 1์ดˆ๊ฐ€ ๋„˜์–ด๊ฐ€๋ฉด ์—๋Ÿฌ์ฒ˜๋ฆฌ
        await asyncio.wait_for(eternity(), timeout=1.0)
    except asyncio.TimeoutError:
        print('timeout!')

asyncio.run(main())
# timeout!

ํ•ด๋‹น ํ•จ์ˆ˜๋Š” ์‹ค์ œ๋กœ ์ทจ์†Œ๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐํ•˜๋ฏ€๋กœ ์ด ๋Œ€๊ธฐ ์‹œ๊ฐ„์ด timeout์„ ๋„˜์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

awaitable

await ํ‘œํ˜„์‹์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ awaitable ๊ฐ์ฒด๋ผ๊ณ  ์นญํ•ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ฐ์ฒด๋Š” ์ฝ”๋ฃจํ‹ด(coroutine), ํƒœ์Šคํฌ(task), ํ“จ์ฒ˜(future)๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด

์ฝ”๋ฃจํ‹ด์€ awaitable ๊ฐ์ฒด์ด๋ฏ€๋กœ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์—์„œ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

async def nested():
    return 42

async def main():
    # ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜๋ฅผ await์„ ์•ˆ๋ถ™์ด๊ณ  ํ˜ธ์ถœํ•˜๋ฉด ํ˜ธ์ถœ๋˜์ง€ ์•Š์Œ
    # ์ฝ”๋ฃจํ‹ด์€ ์ƒ์„ฑ์ด ๋˜์ง€๋งŒ awaitํ•˜์ง€ ์•Š์Œ -> ๊ทธ๋ž˜์„œ ์•„๋ฌด๊ฒƒ๋„ ์‹คํ–‰ํ•˜์ง€ ์•Š๋Š”๋‹ค.
    nested()

    # 42 ๋ฐ˜ํ™˜๋จ
    print(await nested()) 

asyncio.run(main())

์—ฌ๊ธฐ์„œ ์ฝ”๋ฃจํ‹ด์ด๋ž€ ์šฉ์–ด๋Š” ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜์™€ ์ฝ”๋ฃจํ‹ด ๊ฐ์ฒด๋ผ๋Š” ๋‘ ์˜๋ฏธ๋ฅผ ๋‚ดํฌํ•˜๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜: async def ~~ ๋กœ ์ •์˜๋œ ํ•จ์ˆ˜
  • ์ฝ”๋ฃจํ‹ด ๊ฐ์ฒด: ์ฝ”๋ฃจํ‹ด ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•˜๊ณ  ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ์ฒด

asyncio๋Š” ์ด์ „ ๋ฒ„์ „์ธ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜ ์ฝ”๋ฃจํ‹ด๋„ ์ง€์›ํ•œ๋‹ค๊ณ  ํ•˜์ง€๋งŒ 3.10๋ถ€ํ„ฐ๋Š” ์‚ฌ๋ผ์งˆ ๊ธฐ์ˆ ์ด๋ฏ€๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์ด ์ข‹์•„ ๋ณด์ž…๋‹ˆ๋‹ค.

ํƒœ์Šคํฌ

ํƒœ์Šคํฌ๋Š” ์ฝ”๋ฃจํ‹ด์„ ๋™์‹œ์— ์˜ˆ์•ฝํ•˜๋Š”๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์ œ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ asyncio.create_task()์™€ ๊ฐ™์€ ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด ์ฝ”๋ฃจํ‹ด์ด ์‹คํ–‰๋˜๋„๋ก ์ž๋™์œผ๋กœ ์˜ˆ์•ฝํ•ฉ๋‹ˆ๋‹ค.

async def nested():
    return 42

async def main():
    # nested()ํ•จ์ˆ˜๊ฐ€ ๋™์‹œ์— ์‹คํ–‰๋˜๋„๋ก ์˜ˆ์•ฝ
    task = asyncio.create_task(nested())

    # task ๋ณ€์ˆ˜๋ฅผ ์ทจ์†Œํ•˜๊ฑฐ๋‚˜ ์™„๋ฃŒ๋ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ
    await task

asyncio.run(main())

ํ“จ์ฒ˜

ํ“จ์ฒ˜๋Š” ๋น„๋™๊ธฐ ์—ฐ์‚ฐ์˜ ์ตœ์ข… ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋‚ด๋Š” ์ €์ˆ˜์ค€ awaitable ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. ํ˜„์žฌ๊นŒ์ง€ asyncio๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ํ“จ์ฒ˜ ๊ฐ์ฒด๋ฅผ ์–ด๋””์„œ ์‚ฌ์šฉํ•ด์•ผ ๋˜๋Š”์ง€ ์ •ํ™•ํ•˜๊ฒŒ ์ดํ•ด๊ฐ€ ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. asyncio์™€ ํ“จ์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ข‹์€ ์˜ˆ๋Š” loop.run_in_executor() ์ž…๋‹ˆ๋‹ค.

์•„๋ž˜ ์˜ˆ์ œ๋Š” run_in_executor() ์˜ˆ์ œ์ธ๋ฐ ๋ณด๋ฉด ์•Œ๊ฒ ์ง€๋งŒ asyncio๋ฅผ ์‚ฌ์šฉํ•œ ์ฝ”๋ฃจํ‹ด ๋™์ž‘์ด ์•„๋‹Œ, ๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋‚˜ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์Šค๋ฅผ ํ™œ์šฉํ•œ ๋ฐฉ์‹์ž…๋‹ˆ๋‹ค. ์ฝ”๋ฃจํ‹ด์ด ์•„๋‹ˆ๋ฏ€๋กœ ๋‹น์—ฐํžˆ ๋ฌธ๋งฅ ๊ตํ™˜ ๋น„์šฉ์ด ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์žฅ์ ์ด๋ผ๋ฉด ์ผ๋ฐ˜ ํ•จ์ˆ˜๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  ๋น„๋™๊ธฐ๋กœ ๋™์ž‘ํ•˜๊ฒŒ๋” ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

  • ์•„์ง๊นŒ์ง€ ํ“จ์ฒ˜ ๊ฐ์ฒด๋งŒ ์‚ฌ์šฉํ•ด์„œ ์Šค๋ ˆ๋“œ๋‚˜ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹๋ณด๋‹ค asyncio + ํ“จ์ฒ˜๋ฅผ ์‚ฌ์šฉํ•˜๋Š”๋ฐ์— ์ด์ ์ด ๋ฌด์—‡์ธ์ง€๋ฅผ ๋ชจ๋ฅด๊ฒ ์Œ..
import asyncio
import concurrent.futures

def blocking_io():
    # ๋กœ๊น…๊ณผ ๊ฐ™์€ ํŒŒ์ผ ์ž‘์—…์€ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์Šค๋ ˆ๋“œ ํ’€์—์„œ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
    with open('/dev/urandom', 'rb') as f:
        return f.read(100)

def cpu_bound():
    # CPU bound ์ž‘์—…์€ ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์ฐจ๋‹จํ•˜๋ฏ€๋กœ ํ”„๋กœ์„ธ์Šค ํ’€์—์„œ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค.
    return sum(i * i for i in range(10 ** 7))

async def main():
    loop = asyncio.get_running_loop()

    # ์ฒซ ๋ฒˆ์งธ ์ธ์ž๊ฐ€ None์ด๋ฉด ์ž์ฒด์ ์œผ๋กœ ThreadPoolExecutor๋ฅผ ์ƒ์„ฑ - ํ…Œ์ŠคํŠธ ํ–ˆ์„ ๋•Œ, worker๊ฐ€ 41๊ฐœ ๋“ฑ๋ก๋์Œ 
    result = await loop.run_in_executor(None, blocking_io)
    print('default thread pool', result)

    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, blocking_io)
        print('custom thread pool', result)

    with concurrent.futures.ProcessPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, cpu_bound)
        print('custom process pool', result)

asyncio.run(main())

๊ด€๋ จ ๋ฌธ์„œ: https://docs.python.org/ko/3.8/library/asyncio-future.html#asyncio.Future

asyncio๋ฅผ ์‚ฌ์šฉํ•ด API ํ˜ธ์ถœํ•˜๊ธฐ

๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” requests ๋ชจ๋“ˆ์€ ์ฝ”๋ฃจํ‹ด ๊ธฐ๋ฐ˜์œผ๋กœ ๋งŒ๋“ค์–ด์ง„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์— asyncio๋ฅผ ์‚ฌ์šฉํ•ด API ํ˜ธ์ถœ์„ ํ•˜๋ ค๋ฉด aiohttp ๋ผ๋Š” ์ƒˆ๋กœ์šด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” aiohttp๋ฅผ ์‚ฌ์šฉํ•ด API๋ฅผ ํ˜ธ์ถœํ•œ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค. ์•„๋ž˜์— ๋‚˜์˜ค๋Š” uri๋Š” ๊ฒฝ๋กœ์— ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์ž…๋ ฅ๋œ ์ˆซ์ž(์ดˆ ๋‹จ์œ„) ๋งŒํผ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜์ด ์ง€์—ฐ๋ฉ๋‹ˆ๋‹ค.

from time import time
import aiohttp
import asyncio

async def call(session, delay):
    print(f'call {delay}์งœ๋ฆฌ ์‹œ์ž‘')
    # ์ „๋‹ฌ ๋ฐ›์€ session์œผ๋กœ async ์ฒ˜๋ฆฌ
    async with session.get(f'http://httpbin.org/delay/{delay}') as response:
        result = await response.json()
        print(f'call {delay}์งœ๋ฆฌ ๋')
        return result

async def main():
    # ํƒ€์ด๋จธ ์‹œ์ž‘
    start = time()

    # ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์„ธ์…˜ ์ƒ์„ฑ
    async with aiohttp.ClientSession() as session:
        # ์ž‘์—… ๋™์‹œ ์˜ˆ์•ฝ
        two_task = asyncio.create_task(call(session, 2))
        three_task = asyncio.create_task(call(session, 3))

        # await asyncio.sleep(1)
        # ๋‹ค๋ฅธ ์ž‘์—…๋„ ๊ฐ€๋Šฅ

        result1 = await two_task
        result2 = await three_task

        end = time()
        print(end - start)

asyncio.run(main())

# call 2์งœ๋ฆฌ ์‹œ์ž‘
# call 3์งœ๋ฆฌ ์‹œ์ž‘
# call 2์งœ๋ฆฌ ๋
# call 3์งœ๋ฆฌ ๋
# 3.465127944946289

๋” ์ž์„ธํ•œ ์˜ˆ์‹œ๋Š” https://docs.aiohttp.org/en/stable/์— ์ž˜ ์„ค๋ช…๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

asyncio๋ฅผ ์‚ฌ์šฉํ•ด Database ์‚ฌ์šฉํ•˜๊ธฐ

ํ˜„์žฌ asyncio๋ฅผ ์ง€์›ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” PostgreSQL, MySQL, SQLite 3๊ฐ€์ง€์ž…๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ asyncpgaiomysqlaiosqlite ์™€ ๊ฐ™์ด raw ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ๊ณ  ์ด๋ฅผ ํ•œ ๋ฒˆ ๋” ๊ฐ์‹ผ https://github.com/encode/databases ๋ชจ๋“ˆ๋กœ ๊ฐ„ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

์•„๋ž˜๋Š” https://github.com/encode/databases ์—์„œ sqlite๋ฅผ ์ ‘์†ํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š” ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

# Create a database instance, and connect to it.
from databases import Database
database = Database('sqlite:///example.db')
await database.connect()

# Create a table.
query = """CREATE TABLE HighScores (id INTEGER PRIMARY KEY, name VARCHAR(100), score INTEGER)"""
await database.execute(query=query)

# Insert some data.
query = "INSERT INTO HighScores(name, score) VALUES (:name, :score)"
values = [
    {"name": "Daisy", "score": 92},
    {"name": "Neil", "score": 87},
    {"name": "Carol", "score": 43},
]
await database.execute_many(query=query, values=values)

# Run a database query.
query = "SELECT * FROM HighScores"
rows = await database.fetch_all(query=query)
print('High Scores:', rows)

์ข‹์•„์š”7๊ณต์œ ํ•˜๊ธฐ๊ธ€ ์š”์†Œ

Categories: Python Basic

onesixx

Blog Owner

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x