Async
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๊ฐ์ง์ ๋๋ค. ์ฌ๊ธฐ์ asyncpg, aiomysql, aiosqlite ์ ๊ฐ์ด 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๊ณต์ ํ๊ธฐ๊ธ ์์