[Python] anyio - ํ ์ธต ๋ ๊ฐํ๋ ๋น๋๊ธฐ ํจ๋ฌ๋ค์
์ด๋๋ง ์ค๋ฌด์์ Python ๋ฐฑ์๋ ์์ง๋์ด๋ก ๋ณด๋ธ์ง 1๋ ์ด ์กฐ๊ธ ๋์์ต๋๋ค. ๋ง์ ๊ณ ๋ฏผ๊ณผ ๊ณ ๋, ๊ทธ๋ฆฌ๊ณ ์ด ์๋ฆฌ์ ์ค๊ธฐ๊น์ง ์๋ง์ ๋ฐ์ฑ๊ณผ ๋ ธ๋ ฅ์ผ๋ก ํ์ด์ฌ ๋ฐฑ์๋ ์์ง๋์ด๋ก์จ์ ์๋ฆฌ๋ฅผ ์ก์๋ ๊ฒ ๊ฐ์ต๋๋ค.
์ค๋ ์ด์ผ๊ธฐ๋ฅผ ์ํด ๋จ๋์ง์ ์ ์ผ๋ก ๋ง์๋๋ฆฌ์๋ฉด Python์ Java์ Spring๊ณผ ๋ฌ๋ฆฌ ๋๊ธฐ ์ฒ๋ฆฌ๋ณด๋ค๋ ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ๋ ๋์ ๋น์ ์ฑ๋ฅ์ ๋ณด์ธ๋ค๋ ๊ฒ์ ๋๋ค. ํ์ง๋ง ํ์ด์ฌ์ ๋น๋๊ธฐ ํจ๋ฌ๋ค์์ ๊ทธ ์ญ์ฌ๊ฐ ๋งค์ฐ ๋ณต์กํ๋ค๊ณ ํ ์ ์์ ์ ๋๋ก ์๋ง์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์๊ณ ๊ทธ ๋ง์ ๋ ์ฐ๊ธฐ ์ด๋ ค์ด ๋ถ๋ถ์ ์ํฉ๋๋ค.
Coroutine๊ณผ asyncio
Python์ ๋์์ฑ ์ฒ๋ฆฌ๋ Thread, Process์ ๊ฐ์ด ๋๊ธฐ์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ์ํ๋์์ต๋๋ค. ํ์ง๋ง Process๋ฅผ ์ด์ฉํ ๋์์ฑ ์ฒ๋ฆฌ๋ Context switching๊ณผ ๊ฐ์ ๋ณ๋ชฉ์ ์ ๋ฐํ๊ณ , ๊ทธ๋ฅผ ๋์ฒดํด ๋์จ ๊ฒ์ด ๋ฐ๋ก Thread ์ด์ง๋ง ๊ณต๊ต๋กญ๊ฒ๋ Python์ ๋ฉํฐ ์ค๋ ๋ฉ์ GIL๋ก ์ธํ ์์ ์ ์ ์ด์ ์๋ฃจ์ ๋๋ฌธ์ ํต์์ ์ธ ๋ฉํฐ ์ค๋ ๋ ๋ฐฉ์ ๋์์ฑ ์ฒ๋ฆฌ๋ณด๋ค ๋๋ฆฐ ์ฑ๋ฅ์ ๋ณด์ ๋๋ค.
๊ทธ๋์ ๋ํ๋ ๊ฒ์ด ๋ฐ๋ก Coroutine์ ๋๋ค. Coroutine์ ํ์ถ ์ง์ (return ์ง์ )์ด ์์ ๋ก์ด ํจ์๋ฅผ ์ผ์ปซ๋ ๋ง๋ก ์ฐ๋ฆฌ๊ฐ ํต์ ๊ตฌํํ๋ ํจ์๋ return ์ง์ ์ด ์ ํด์ ธ ์์ด, ๊ทธ ์ง์ ์ ๋น ์ ธ ๋๊ฐ์ผ๋ง ๋ค์ ์์ ์ ํ ์ ์๋ ๋ฐ๋ฉด Coroutine์ ๊ทธ ์ง์ ์ด ์ด๋ ์์ ์์๋ ์ง ๋์ํฉ๋๋ค.
ํ์ง๋ง Python์ Coroutine์ ์ฌ์ฉํ๊ธฐ ๊ฐ๋จํ์ง ์์์ต๋๋ค. (์ด์ฉ๋ฉด Javascript์ Promise ๋ณด๋ค๋ ๋ ๋ณต์กํ ์ง๋..) ์ด ์ฝ๋ฃจํด์ด ๊ฐ๋จํด์ง๊ธฐ๊น์ง Python ์ง์์์๋ 3๊ฐ์ง ์ฝ๋ฃจํด ๊ตฌํ ๋ฐฉ๋ฒ์ด ์กด์ฌํ๊ณ ์์ต๋๋ค.
(์๋์ ํญ๋ชฉ๋ค์ ํด๋ฆญํ๋ฉด ์์ธํ ์ฝ๋๋ฅผ ๋ณผ ์ ์์ต๋๋ค.)
-
Generator based Coroutine
Python์ Generator๋ ๊ฐ์ ์์ฑํ๋ ํจ์๋ก yield ํค์๋๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋์ ๋๋ค. ํจ์๊ฐ ๊ฐ์ ๋ฐํํ ํ ์ค์ฝํ๋ฅผ ์๋ฉธํ ๋ค์, ๋ค์ ํจ์๋ฅผ ํธ์ถํ๋ฉด ์ฒ์๋ถํฐ ๋ค์ ์์ํ๋ ๊ธฐ๋ฅ์ธ๋ฐ, ์ด ๊ธฐ๋ฅ์ ์ผ์ ์ค์ง๋ก ์ฌ์ฉํ๋ฉฐ ์ฝ๋ฃจํด์ ๊ตฌํํ ๋ฐฉ๋ฒ์ด ๋ฐ๋ก Generator based corutine ์ ๋๋ค.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersdef coro(): hello = yield "Hello" yield hello c = coro() print(next(c)) print(c.send("World")) ํ์ง๋ง ์ด๋ฌํ syntax๋ ์ด ๋ฌธ๋ฒ์ด ํจ์์ธ์ง, ์ ๋ค๋ ์ดํฐ์ธ์ง, ํน์ ์ฝ๋ฃจํด์ธ์ง๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ด๋ ต๊ฒ ํฉ๋๋ค. ์์ ์ฝ๋๋ ์ฝ๋ฃจํด์ด๋ฉฐ ํจ์ ์ปจํ ์คํธ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ๋ yield๋ฅผ ํตํด ์ผ์์ค์งํ๊ณ ๊ทธ ๊ฐ์ ๊ฐ์ ธ์ค๋ ๋ชจ์ต์ ๋๋ค.
์ด๋ฅผ ์ฐ๋ฆฌ๋ ์ ๋ค๋ ์ดํฐ ๊ธฐ๋ฐ ์ฝ๋ฃจํด์ด๋ผ๊ณ ํฉ๋๋ค. -
asyncio Decorator based Coroutine
asyncio์ ๋ชจ๋์ ์ด์ฉํ์ฌ ์ฝ๋ฃจํด์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ธ๋ฐ, ์ฌ์ค์ ์ ๋ค๋ ์ดํฐ ๊ธฐ๋ฐ ์ฝ๋ฃจํด๊ณผ ๋ค๋ฅธ์ ์ asyncio ๋ชจ๋ ์์์ ๋์์ํจ ๋ค๋ ๊ฒ์ ์ฐจ์ด์ ๋๋ค.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport asyncio import datetime import random @asyncio.coroutine def display_date(num, loop): end_time = loop.time() + 50.0 while True: print("Loop: {} Time: {}".format(num, datetime.datetime.now())) if (loop.time() + 1.0) >= end_time: break yield from asyncio.sleep(random.randint(0, 5)) loop = asyncio.get_event_loop() asyncio.ensure_future(display_date(1, loop)) asyncio.ensure_future(display_date(2, loop)) loop.run_forever() ์ ๋ค๋ ์ดํฐ ๊ธฐ๋ฐ์ ์ฝ๋ฃจํด์ด ํ์ด์ฌ์ ์ค์ฝํ์ ์ํด ๋์๋๋ ๊ฒ์ด๋ผ๋ฉด asyncio๋ ์ด๋ฒคํธ ๋ฃจํ์ ํด๋นํ๋ฉฐ ํ๋ฅผ ์ด์ฉํด ํ์คํฌ๋ฅผ ์ ์ฌํ๊ณ ์ผ์ ์ค์ง๊ฐ ๋ฐ์ํ๋ฉด ์ํ๋ฅผ ์ ์ฅํ ๋ค์ ๊ทธ ์ํ์ ๋ฐ๋ผ ํจ์๋ฅผ ์คํ์์ผ์ฃผ๋ ๋ฐฉ์์ ๋๋ค.
์ ์ฝ๋๋ฅผ ๋ณด๋ฉด yield from ํค์๋๊ฐ ๋ณด์ด๋๋ฐ, yield from์ yield ๋ฐ๋ณต๋ฌธ์ ๊ฐ๋จํ๊ฒ ํ๊ธฐ ์ํ syntax๋ก ๋ณธ๋๋ for x in asyncio.sleep(random.randint(0, 5)): yield x ์ฝ๋๋ฅผ ์ค์ธ ๊ฒ์ ๋๋ค.
์ด์ฒ๋ผ asyncio ๋ชจ๋์ ์ด์ฉํด ๋น๋๊ธฐ๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์ asyncio ๋ชจ๋ ๊ธฐ๋ฐ ์ฝ๋ฃจํด์ด๋ผ๊ณ ํฉ๋๋ค. -
Native Coroutine
๋ค์ดํฐ๋ธ ์ฝ๋ฃจํด์ Python 3.5์์ ๋ฑ์ฅํ ๋น๋๊ธฐ ์ฒ๋ฆฌ ๋งค์ปค๋์ฆ์ผ๋ก async-await ํค์๋๊ฐ ์ฌ๊ธฐ์ ํด๋นํฉ๋๋ค.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersimport asyncio import datetime import random async def display_date(num, loop, ): end_time = loop.time() + 50.0 while True: print("Loop: {} Time: {}".format(num, datetime.datetime.now())) if (loop.time() + 1.0) >= end_time: break await asyncio.sleep(random.randint(0, 5)) loop = asyncio.get_event_loop() asyncio.ensure_future(display_date(1, loop)) asyncio.ensure_future(display_date(2, loop)) loop.run_forever() ํจ์์ ์ ์ธ์ def๊ฐ ์๋ async def๋ฅผ ์ด์ฉํ๊ณ ์์ผ๋ฉฐ ๊ฐ์ ๋ฐํ ์ญ์ yield๋ yield from์ด ์๋ await๋ฅผ ์ฌ์ฉํ๊ณ ์๋ ๋ชจ์ต์ ๋๋ค. ์ง๊ธ ๋๋ถ๋ถ์ ํ์ด์ฌ ๊ฐ๋ฐ์ ๋ถ๋ค์ด ์ฌ์ฉํ๋ ๋ฐฉ์์ผ ๊ฒ์ ๋๋ค.
๋น๋๊ธฐ ์ฒ๋ฆฌ ๋งค์ปค๋์ฆ์ ๋ํด์๋ ๋น์ Javascript ์ง์์์๋ ํ๋๊ฐ ๋์๋ ๊ฒ์ด์์ต๋๋ค. ํ์ง๋ง ์ด๋ฐ ๋ค์ํ ๋งค์ปค๋์ฆ๋ค์ ์คํ๋ ค ๋ ๊ฑฐ์ ์ฒ๋ฆฌ๋ฅผ ์ด๋ ต๊ฒ ํ๋ ๋ถ๋ถ์ด ์๊ณ , ์ด๋ฒคํธ ๋ฃจํ ๋ชจ๋์ ๋ํ ์ฌ์ฉ๋ฒ์ ์์์ผํ๋ ๋ฑ ํ์ด์ฌ์ผ๋ก ๋น๋๊ธฐ๋ฅผ ๊ฐ๋ฐํ๋ ๋ฐ ์์ด์ ๋์ ๋ฌ๋ ์ปค๋ธ๋ฅผ ๋ณด์ด๊ฒ ํ์ต๋๋ค.
์ด์ ๋น์ทํ๊ฒ Javascript V8์ ๊ฒฝ์ฐ๋ ์ฑ๊ธ ์ค๋ ๋์ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ด์ฉํ์ง๋ง ์ด๋ฌํ ์ด๋ฒคํธ ๋ฃจํ์ ๋ํด์๋ ๊ฐ๋ฐ์๊ฐ ์ ๊ฒฝ์ฐ์ง ์์๋ ๋์ด ์ฌ์ค์ ๊ฐ๋ฐ์๊ฐ ์ฝ๊ฐ ์๋ฒ ๊ฐ๋ฐ์ ์ง์คํ ์ ์๋๋ก ํด์ฃผ๋ ์ ๊ณผ ๋ค์ ์ฐจ์ด๋ฅผ ๋ณด์ ๋๋ค.
asyncio์ ๋ณต์ก์ฑ
asyncio๋ ์ผํ ์ด ๊ธ๋ก๋ง ๋ณด๋ฉด ๋จ์ํ ์ด๋ฒคํธ ๋ฃจํ์ฒ๋ผ ๋ณด์ด์ง๋ง ๊ทธ ๋ด๋ถ์์๋ ๋ฌด์ํ ๋ง์ ๊ธฐ๋ฅ๋ค์ ํฌํจํ๊ณ ์์ต๋๋ค. ๋ถ๋ช asyncio๋ ๋ชจ๋ ์์ฒด๋ ํ๋ฅญํ์ง๋ง ์ฅ๋ฒฝ ๋์ ์ ๊ทผ์ฑ์ ๊ฐ๋ฐ์๋ค์๊ฒ ์คํ๋ ค ๊ฑฐ๋ฆฌ๊ฐ๋ง ์ฃผ๊ฒ ๋์์ต๋๋ค.
์ ๋ํ asyncio ๋ชจ๋์ ์ฌ์ฉํ๊ณ ์์ง๋ง asyncio๋ ๋จ์ํ ์ด๋ฒคํธ ๋ฃจํ๊ฐ ์๋์์ต๋๋ค. ์ค์ ๋ก ์ ๊ฐ ์๊ฐํ๋ ์ด๋ฒคํธ ๋ฃจํ, ์ฌ๋ฌ๋ถ๋ค์ด ์๊ฐํ๋ ์ด๋ฒคํธ ๋ฃจํ๋ ๊ฐ ๋จ์ผ ์ค๋ ๋ ๋น ํ๋์ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ๊ฐ์ง๋ ๊ฒ์ผ๋ก ์๊ฐํ์ค ๊ฒ๋๋ค.

ํ์ง๋ง Python์ asyncio๋ ์ด์ ๊ฐ์ด ๋์ํ์ง ์์ต๋๋ค. ๋ฉ์ธ ์ค๋ ๋์์ get_event_loop ํจ์๋ฅผ ์ด์ฉํด ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ์์ฑํ ์ ์๋๋ฐ, ์ด๋ฅผ ๋ค๋ฅธ ์๋ธ ์ค๋ ๋๋ฅผ ๋ง๋ค๊ณ ์์ฑํ๋ฉด ์ค๋ฅ๊ฐ ๋ฐ์ํฉ๋๋ค.

๋ง์ฝ ์๋ธ ์ค๋ ๋์์ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด set_event_loop ํจ์๋ฅผ ํตํด ๋ฉ์ธ ์ค๋ ๋์์ ์์ฑํ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ๋ฐ์ธ๋ฉํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด๋ ๋ฉ์ธ ์ค๋ ๋์ ์๋ธ ์ค๋ ๋๊ฐ ๊ฐ์ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ์ฌ์ฉํ ์๋ ์๋ค๋ ๊ฒ์ ๋๋ค.
์๋ธ ์ค๋ ๋์์ ๋ ๋ฆฝ๋ ๋ค๋ฅธ ์ด๋ฒคํธ ๋ฃจํ๋ฅผ ์ฌ์ฉํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ new_event_loop ํจ์๋ฅผ ํธ์ถํด ์ฌ์ฉํ ์ ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ, ๋ฌธ์ ์ ์ ์ด๋ฐ ๊ฒฝ์ฐ ์๋ธ ์ค๋ ๋์์ get_event_loop๋ฅผ ํธ์ถํ์ ๋ ์์์ ์์ฑํ ์๋ก์ด ์ด๋ฒคํธ ๋ฃจํ๊ฐ ๋ฐํ๋์ด์ผ ํ์ง๋ง ๊ทธ๋ ์ง ์๋ค๋ ๊ฒ์ ๋๋ค.
์ด๋ฌํ ๋ฌธ์ ๋ ์คํ๋ ค asyncio๋ฅผ ์ดํดํ๊ธฐ ์ด๋ ต๋ค๋ ํผ๋๋ฐฑ์ผ๋ก ๋จ๊ฒ ๋ฉ๋๋ค.
anyio
asyncio๊ฐ ํ์ด์ฌ ๋น๋๊ธฐ์ ๊ฑฐ์ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ฒ์ฌ๋ ๊ฒ์ ๋ง์ง๋ง ์ฌ์ ํ ํ์ด์ฌ์์ ๋น๋๊ธฐ๋ฅผ ๊ตฌํํ ์ ์๋ ๋ค์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์กด์ฌํฉ๋๋ค. ๊ทธ ์ค์์ anyio๋ ํ์ฌ FastAPI(์ ํํ๋ ์ค๋ฆฌ์ง๋ ํ๋ ์์ํฌ์ธ Starlette)์์ ์ฌ์ฉ ์ค์ธ ๋น๋๊ธฐ ๋ชจ๋์ ํด๋นํฉ๋๋ค.
anyio๋ ์๋ก์ด ๊ฐ์ฒ๋ ๋น๋๊ธฐ ๋ชจ๋์ด ์๋ asyncio ํน์ trio ์์์ ๋์ํ๋ ๊ทธ๋ค์ ๊ตฌํ์ฒด์ ํด๋นํฉ๋๋ค. asyncio๋ ๊ทธ ๊ธฐ๋ฅ์ด ํ๋ฅญํ์ง๋ง ์ฌ์ฉ์ด ์ด๋ ค์ ๋ ๋ถ์ ์ ์ธ ํผ๋๋ฐฑ์ ๋ฐ์๊ณ , trio ์ญ์ ๊ฐ๋จํ ์ฌ์ฉ์ฑ์ผ๋ก ๋ด์ธ์ ์ง๋ง ๊ธฐ๋ฅ์ด ๋ถ์คํด ์ฐจ๊ฐ์ด ์์ ์ ๋ฐ์๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค.
anyio์ ํน์ง์ trio๊ฐ ๊ฐ์ง async-await์ ์์ฃผ ๊ฐ๋จ๋ช ๋ฃํ ๊ฐ๋ ์ asyncio์ ์ ๋ชฉ ์ํจ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋๋ค. ์ ๋ค๋ ์ดํฐ ์ฝ๋ฃจํด์ด๋ ๋ชจ๋ํ ์ฝ๋ฃจํด๊ณผ ๊ฐ์ ๊ตฌ์กฐํ๋ ๋์์ฑ ๊ฐ๋ ์ด ์์ด ์ฌ์ฉ์ด ํธ๋ฆฌํ๊ณ ๊ฐ๋ฐ์ ๋์ฑ ์ง์คํ ์ ์๊ฒ ํด์ค๋๋ค.
๋จผ์ ์ค์น๋ถํฐ ์ฐจ๊ทผ์ฐจ๊ทผ ํด๋ณด๊ณ ๋ฌด์์ด ๋ค๋ฅธ์ง๋ฅผ ์์๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
Installation
anyio๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ Python 3.6.2 ๋ฒ์ ์ด์์ ์ฌ์ฉํด์ผ ํ์ง๋ง ๋ชจ๋ ๊ธฐ๋ฅ์ ์์ ํ๊ฒ ๋์ํ๊ธธ ์ํ๋ค๋ฉด Python 3.7 ๋ฒ์ ์ด์ ์ฌ์ฉ์ ๊ถ์ฅํฉ๋๋ค.
$ pip install anyio
$ pip install anyio[trio]
asyncio๊ฐ ์๋ trio๋ฅผ ๋ฐฑ์๋๋ก ์ฌ์ฉํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ extra ์ต์ ์ trio๋ฅผ ๋ฃ์ ์ ์์ต๋๋ค.
Create Task
๊ฐ๋จํ ํ์คํฌ๋ฅผ ํ๋ ๋ง๋ค์ด๋ณด๊ฒ ์ต๋๋ค.
from anyio import sleep, create_task_group, run | |
async def task(num): | |
print('Task', num, 'running') | |
await sleep(1) | |
print('Task', num, 'finished') | |
async def main(): | |
async with create_task_group() as tg: | |
for num in range(5): | |
tg.start_soon(task, num) | |
print('All tasks finished!') | |
run(main) | |
# ๋ง์ฝ trio๋ฅผ ๋ฐฑ์๋๋ก ์ฌ์ฉํ๋ ค๋ ๊ฒฝ์ฐ ์๋์ ์ฝ๋ ์ฃผ์์ ํด์ | |
# run(main, backend='trio') |
anyio์์ ์์ ์ ์์ฐจ์ ์ผ๋ก ์์ฑํด ๋น๋๊ธฐ ์ฒ๋ฆฌ๊ฐ ๋๋์ง๋ฅผ ๋ณด๊ธฐ ์ํด์ create_task_group์ ์ด์ฉํ ์ ์์ต๋๋ค. ์ด task_group์ ํ์ด์ฌ์ ๋น๋๊ธฐ ์ปจํ ์คํธ ๊ด๋ฆฌ์๋ฅผ ์ฌ์ฉํ์ฌ ๊ตฌํํ ์ ์๊ณ , ํจ์์ ์ธ์๋ฅผ ๋ฃ์ผ๋ ค๋ฉด task_group์ start_soon ๋ฉ์๋๋ฅผ ์ด์ฉํ๋ฉด ๋ฉ๋๋ค.

๊ทธ๋ฌ๋ฉด ์์ฐจ์ ์ผ๋ก Task 0 ~ 5 running์ด ์คํ๋์๋ค๊ฐ 1์ด ๋ค finished๊ฐ ๋ํ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
Handling Exception
๋์์ฑ ํ๋ก๊ทธ๋๋ฐ์ ํ๋ค๋ณด๋ฉด ๋์ ๋ค๋ฐ์ ์ผ๋ก ์ค๋ฅ๊ฐ ๋ฐ์ํ ์๋ ์๋๋ฐ, ์ด๋ฐ ๋ค๋ฐ์ ์ค๋ฅ๋ฅผ anyio์์๋ ํ ๋ฒ ์ค๋ฅ ๋ฐ์์ผ๋ก ๋๋๋ ๊ฒ์ด ์๋ ๋ชจ๋ ์ค๋ฅ๋ฅผ ๋ด๋ฑ์ด ์ค์ผํ๋ค. ์ฆ, ๋์ ๋ค๋ฐ์ ์ธ ์ค๋ฅ๋ ๊ทธ ์ญ์ถ์ ์ ๋ชจ๋ ํฌํจํด์ผ ํ๋ค๋ผ๋ ๋ง์ธ๋๋ฅผ ๊ฐ์ง๊ณ ๋์ํฉ๋๋ค.
anyio๋ ๋ฐ๋ก ์์ task_group๊ณผ ๋น์ทํ ExceptionGroup์ ์ฌ์ฉํฉ๋๋ค.
from anyio import sleep, create_task_group, run | |
async def task(number): | |
print('Task', number, 'is running') | |
await sleep(1) | |
if number == 2: | |
raise ValueError | |
if number == 4: | |
raise TypeError | |
print('Task', number, 'finished') | |
async def main(): | |
async with create_task_group() as tg: | |
for i in range(5): | |
tg.start_soon(task, i) | |
print('All tasks finished!') | |
run(main) |

์ฝ๋ ๊ตฌํ๋๋ก running ๊น์ง๋ ๋ชจ๋ ๋์ํฉ๋๋ค. ๊ทธ ๋ค์์ ๋์ค๋ ๊ฒ์ด ๋ฐ๋ก ExceptionGroup์ธ๋ฐ, ๊ทธ ๋ฐ์ผ๋ก ์ด๋ค ์ค๋ฅ๊ฐ ๋ํ๋ฌ๋์ง๋ฅผ ์๋ ค์ค๋๋ค.

๊ตฌํ๋๋ก ๋ช ๋ฒ์งธ ๋ผ์ธ์์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค.
์ฌ๊ธฐ์ ์ฃผ์ํด์ผํ ์ ์ ๋น๋๊ธฐ ์ฒ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์ ์ค๋ฅ ๋ฐ์์ ์์๋ ๋ณด์ฅ๋์ง ์๋๋ค๋ ์ ์ ๋๋ค. ํ๋ฉด์์๋ running์ด ๋ชจ๋ print ๋๊ณ ๋ ๋ค์ Exception์ด ํธ๋ค๋ง ๋์์ง๋ง ๋๋ก๋ ๋ชจ๋ finished ๋ ๋ค์์ Error๊ฐ ํธ๋ค๋ง ๋๊ธฐ๋ ํฉ๋๋ค.
Synchronization primitives
๋ชจ๋ ๋์์ฑ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ง์ฐฌ๊ฐ์ง๋ก anyio์๋ Lock(๋ฝ), Semaphore(์ธ๋งํฌ์ด), Event(์ด๋ฒคํธ) ๋ฑ ์์ ์ ์กฐ์ ํ๊ธฐ ์ํ ๋ชจ๋ ๋๊ธฐํ ์์๋ฅผ ์ ๊ณตํฉ๋๋ค.
- Event (์ด๋ฒคํธ)
- Semaphore (์ธ๋งํฌ์ด)
- Lock (๋ฝ)
- Condition (์ปจ๋์ )
- Capacity Limiter (์ฉ๋ ๋ฆฌ๋ฏธํฐ)
์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ ๋ค์ ์์ํ Event์ Capacity Limiter ๋ ๊ฐ์ง๋ง์ ์์๋ก ๋ค๋ค๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค.
from anyio import Event, create_task_group, run, sleep | |
async def task(event: Event, number: int): | |
print('Task', number, 'is waiting') | |
await event.wait() | |
print('Task', number, 'finished') | |
async def main(): | |
event = Event() | |
async with create_task_group() as tg: | |
for i in range(10): | |
tg.start_soon(task, event, i) | |
await sleep(1) | |
await event.set() | |
run(main) |
์ ์ฝ๋๋ 10๊ฐ์ Task๊ฐ event๋ผ๋ ์์ ํ ๊ฐ์ ๋ํ ๋์ ์ ๊ทผ ์ ํ ์ฝ๋์ ๋๋ค. Event๋ ์ฌ์ฌ์ฉ๋์ง ๋ชปํ๊ณ ํ ๋ฒ ์ฌ์ฉํ ๋ค์ ๋ฒ๋ ค์ง๊ฒ ๋ฉ๋๋ค. ๋ง์ฝ ํด๋น ์ด๋ฒคํธ๋ฅผ ์ฌ์ฌ์ฉํ๊ณ ์ ํ๋ค๋ฉด ๋๊ฐ์ ์ด๋ฒคํธ ์ธ์คํด์ค๋ฅผ ํ ๊ฐ ๋ ๋ง๋ค์ด ์คํํด์ผ ํฉ๋๋ค.
from anyio import CapacityLimiter, create_task_group, sleep, run | |
async def use_resource(task_num, limiter): | |
async with limiter: | |
print('Task number', task_num, 'is now working with the shared resource') | |
await sleep(1) | |
async def main(): | |
limiter = CapacityLimiter(2) | |
async with create_task_group() as tg: | |
for num in range(10): | |
tg.start_soon(use_resource, num, limiter) | |
run(main) |
Capacity Limiter์ ์ ํํ ํด์์ ์ด๋ป๊ฒ ๋๋์ง ๋ชจ๋ฅด๊ฒ ์ง๋ง ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ธ๋งํฌ์ด์ ๋น์ทํฉ๋๋ค. ํํํ๋ค๋ฉด ์ฌ๋ฌ๊ฐ์ ์ธ๋งํฌ์ด๋ฅผ ํ๋์ ๊ฐ์ฒด๋ก ์ฌ์ฉํ๋ค๋ ๊ฒ์ด ๋ง์ ์๋ ์์ ๊ฒ ๊ฐ๋ค์.
๋ฆฌ๋ฏธํฐ๋ฅผ 2๊ฐ๋ก ์ฃผ๊ณ 10๊ฐ์ Task๋ฅผ ์คํ์ํต๋๋ค. ๊ทธ๋ฌ๋ฉด ์ต์ด๋ก ๋ฐ์ 2๊ฐ์ Task๊ฐ ์คํ๋ฉ๋๋ค.

๊ทธ๋ฐ ๋ค์๋ ํ์ ์์ธ๋๋ก ๋ง์ง๋ง์ ๋ค์ด๊ฐ Task๊ฐ ๊ทธ ๋ค์ ์์์ ํ๋ณดํ๊ณ ์์ฐจ์ ์ผ๋ก ์คํํฉ๋๋ค.
anyIO์ ๋ํด ๋ ์์๋ณด๊ณ ์ถ๋ค๋ฉด ์๋์ ๋งํฌ๋ฅผ ์ฐธ๊ณ ํด๋ณด์ธ์.
https://anyio.readthedocs.io/en/stable/index.html
AnyIO โ AnyIO 3.4.0 documentation
ยฉ Copyright 2018, Alex Grรถnholm. Revision 5376d62a.
anyio.readthedocs.io
๋ง์น๋ฉฐ..
์ด๋ฒ ํฌ์คํธ์์๋ Python์ผ๋ก 1๋ ๋์ ๋ฐฑ์๋ ์์ง๋์ด๋งํ๋ฉด์ ์ป์ ๋น๋๊ธฐ ์ง์, ๊ทธ๋ฆฌ๊ณ ๊ทธ์ ๋ํ ๊ฐ๋จํ ํ๊ธฐ๋ฅผ ํฌํจํ์ฌ ํ Python์ ํํฉ์ ๋ํด ์ ์ด๋ณด๊ฒ ๋์์ต๋๋ค.
์๋ง Java๋ Javascript๋ฅผ ์ด์ฉํ๊ณ ๊ณ์ ๋ฐฑ์๋ ์์ง๋์ด ๋ถ๋ค์ด ์ด ๊ธ์ ๋ณด์ ๋ค๋ฉด "๋ด๊ฐ ์๊ณ ์๋ Python์ด ์ด๋ ๊ฒ ์ด๋ ค์ด ๊ฑฐ์๋..?"๋ผ๋ ๋ง์์ด๋ ์๊ฐ์ ํ์ค์ง๋ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค. ํ์ง๋ง ๊ณผ๊ฑฐ์ ๋นํ๋ฉด ๋ง์ด ์ฌ์์ง๊ณ ์๋ค๋ ๊ฒ์ ๋ง๊ณ , ๋ค๋ฅธ ์ธ์ด ์ํ๊ณ์ ๋นํ๋ฉด ๋ง์ด ๋ถ์กฑํ๋ค๋ ๊ฒ๋ ๋ถ์ ํ๊ธฐ๋ ์ด๋ ต์ต๋๋ค.
์ด๋ฒ์ ๋์จ anyio๋ asyncio์์ ๊ฐ์ง๊ณ ์์๋ ๋ง์ ๋ถํธํจ ๊ทธ๋ฆฌ๊ณ ๋์์ฑ ํ๋ก๊ทธ๋๋ฐ์ ์ํ ๋งค์ปค๋์ฆ์ ๋์ฑ ๊ฐ๋จํ๊ฒ ์ฌ์ฉํ๊ณ ์ฌํ๊น์ง์ ๋น๋๊ธฐ ํจ๋ฌ๋ค์์ ์์ด ๋ถํธํ ์ ๋ค์ ํ๋๋ชจ์ ๊ฐ์ ํ๋ค๋ ์ ์ ๊ฐ์ฅ ํฐ ์ฃผ์์ ์ผ๋ก ๋ฝ๊ณ ์์ต๋๋ค.
๊ทธ๋๋ง ๋คํ์ธ ๊ฒ์ anyio๋ผ๋ ์ด๋ฐ ์ข์ ํจ๋ฌ๋ค์์ด ๊ธฐ์กด์ asyncio์ค ํธํ๋๋ค๋ ๊ฒ์ ๋คํ์ธ ์ผ์ ๋๋ค. ํ์ง๋ง ์ด๋ฏธ asyncio ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๋ฐ๋ SQLAlchemy 1.4๋ aiohttp์ ๊ฐ์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค์ด ์ด๋ค๋ก ๋ฆฌํฉํฐ๋งํด ๋ฐ๊ฟ๋๊ฐ๋ ๊ฒ์ ๊ทธ๋งํผ ๋ง์ ์๊ฐ์ด ์๋ชจ๋๋ฉฐ ์์ง๊น์ง๋ ํ์ด์ฌ ๋ด ๋น๋๊ธฐ ํจ๋ฌ๋ค์์ ํตํฉ๋์ง ๋ชปํ๊ณ ๋ถ์ฐ๋์๋ค๋ ๊ฒ์ ๋๋ค.
ํ์ด์ฌ์ด ๊ทธ๋งํผ ์์ ๋ก์ด ์ธ์ด์ธ ๊ฒ์ ๋ณด์ฅ๋ฐ์ ์ ์์ง๋ง ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ฐ๋ฐํ๋ ํ ์์ง๋์ด๋ก์จ ์ด๋ฌํ ๋ชจ์ต์ ์คํ๋ ค ๊น์ ๋ ๊ฑฐ์๋ฅผ ๋์ฑ ์ ๋ฐํ๊ณ ๊ด๋ฆฌํ๊ธฐ ์ด๋ ค์ด ํ๋ก๋ํธ๋ฅผ ๋ง๋๋ ๋ฐ ์์ธ์ด ๋๋ ์ธ์ด๊ฐ ๋์ง ์์ ์ฐ๋ ค์ค๋ฝ์ต๋๋ค.
์ฐธ๊ณ : AnyIO ๊ณต์ ๋ฌธ์ (https://anyio.readthedocs.io/en/stable/index.html)