๐Ÿ”„ ํŒŒ์ด์ฌ์˜ ๋น„๋™๊ธฐ: ์ฝ”๋ฃจํ‹ด์—์„œ ์‹œ์Šคํ…œ์ฝœ๊นŒ์ง€

L-cloudยท2025๋…„ 4์›” 24์ผ
0

ํŒŒ์ด์ฌ

๋ชฉ๋ก ๋ณด๊ธฐ
6/6
post-thumbnail

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

๋น„๋™๊ธฐ๋ž€?

"๋น„๋™๊ธฐ๋Š” ์–ด๋–ค ์ž‘์—…์„ ์š”์ฒญํ•˜๊ณ  ๋๋‚  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์ผ์„ ํ•˜๋Š” ๋ฐฉ์‹"์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋™์‹œ์„ฑ์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๊ผญ ๋ณ‘๋ ฌ์„ฑ์ด ์•„๋‹ˆ์–ด๋„ ๋ฉ๋‹ˆ๋‹ค. ๋ณดํ†ต ํ•จ์ˆ˜์˜ ์‹คํ–‰์„ ์ค‘๋‹จ(suspend)ํ•˜๊ณ  ๋‚˜์ค‘์— ๋‹ค์‹œ ์žฌ๊ฐœ(resume)ํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ˆ˜ํ•œ ํ•จ์ˆ˜์ธ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ์ด๊ฒƒ์„ ๊ตฌํ˜„ํ•˜๊ณ ๋Š” ํ•ฉ๋‹ˆ๋‹ค.

์ฝ”๋ฃจํ‹ด์ด๋ž€?

์ผ๋ฐ˜์ ์œผ๋กœ ์ฝ”๋ฃจํ‹ด์€ 'ํ•จ์ˆ˜์˜ ์‹คํ–‰์„ ์ค‘๋‹จ(suspend)ํ•˜๊ณ  ๋‚˜์ค‘์— ๋‹ค์‹œ ์žฌ๊ฐœ(resume)ํ•  ์ˆ˜ ์žˆ๋Š” ํŠน์ˆ˜ํ•œ ํ•จ์ˆ˜'๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰ ์ผ๋ฐ˜ ํ•จ์ˆ˜๋Š” return๋˜๋ฉด ๋์ด์ง€๋งŒ, ์ฝ”๋ฃจํ‹ด์€ ์ค‘๊ฐ„์— ๋ฉˆ์ท„๋‹ค๊ฐ€ ๋‚˜์ค‘์— ๋‹ค์‹œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

asyncio์™€ await์ด ๋„์ž…๋˜๊ธฐ ์ „ ํŒŒ์ด์ฌ์€ ์ œ๋„ˆ๋ ˆ์ดํ„ฐ๋กœ ์ฝ”๋ฃจํ‹ด์„ ๊ตฌํ˜„ ํ•˜๊ณ ๋Š” ํ–ˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ์•„์ฃผ ๊ฐ„๋‹จํ•œ ์˜ˆ์‹œ ์ฝ”๋“œ์ž…๋‹ˆ๋‹ค.

def my_coroutine():
 print("Start")
 x = yield "Paused at yield"
 print(f"Resumed with x = {x}")

# ์‚ฌ์šฉ
coro = my_coroutine()
print(next(coro))        # "Start" โ†’ "Paused at yield"๋ฅผ ๋ฐ˜ํ™˜ํ•จ... ๋‹ค๋ฅธ ๋กœ์ง
print(coro.send(42))     # "Resumed with x = 42" send๋กœ x์— 42๋ฅผ ์ฃผ์ž…(?)

next(coro)๋Š” ์‹คํ–‰์„ ์‹œ์ž‘ํ•˜๊ณ  ์ฒซ yield์—์„œ ์ค‘๋‹จํ•ฉ๋‹ˆ๋‹ค. ์ดํ›„ ๋‹ค๋ฅธ ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋‹ค send(42)๋ฅผ ํ†ตํ•ด ์ฝ”๋ฃจํ‹ด์— ๊ฐ’์„ ๋ณด๋‚ด๊ณ  ์žฌ๊ฐœํ•ฉ๋‹ˆ๋‹ค.

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

์ด๋Ÿฌํ•œ ๋กœ์ง์„ ์ˆ˜๋™์œผ๋กœ ์ž‘์„ฑํ•˜๋ ค๋ฉด ์ƒํƒœ ๊ด€๋ฆฌ, ์—๋Ÿฌ ํ•ธ๋“ค๋ง, ์‹คํ–‰ ์ˆœ์„œ ์กฐ์ • ๋“ฑ์„ ๋ชจ๋‘ ๊ณ ๋ คํ•ด์•ผ ํ•˜๋ฉฐ, ์ฝ”๋ฃจํ‹ด ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ์œ ์ง€๋ณด์ˆ˜๋„ ์–ด๋ ค์›Œ์ง‘๋‹ˆ๋‹ค. ์ƒ๊ฐ๋งŒ ํ•ด๋„ ๋ฒˆ๊ฑฐ๋กญ๊ณ  ๋ณต์žกํ•˜์ฃ . ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒƒ์ด ๋ฐ”๋กœ ์—ฌ๋Ÿฌ ์ฝ”๋ฃจํ‹ด์˜ ์‹คํ–‰๊ณผ ์ค‘๋‹จ, ์™„๋ฃŒ ์ƒํƒœ๋ฅผ ์ž๋™์œผ๋กœ ๊ด€๋ฆฌํ•ด ์ฃผ๋Š” โ€˜์ด๋ฒคํŠธ ๋ฃจํ”„(event loop)โ€™์ž…๋‹ˆ๋‹ค.

ํŒŒ์ด์ฌ์—์„œ๋Š” ์ด ๊ฐœ๋…์„ ํ‘œ์ค€ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ˆ˜์ค€์—์„œ ์ œ๊ณตํ•˜๋ฉฐ, ๊ทธ ๊ตฌํ˜„์ฒด๊ฐ€ ๋ฐ”๋กœ asyncio์ž…๋‹ˆ๋‹ค.

asyncio, Future, Task

asyncio๋Š” ์–ด๋–ป๊ฒŒ ์ž‘์—…์˜ ์™„๋ฃŒ ์—ฌ๋ถ€, ์ค‘๋‹จ ์‹œ์ , ์žฌ๊ฐœํ•  ํƒ€์ด๋ฐ ๋“ฑ์„ ๋ชจ๋‘ ๊ด€๋ฆฌํ• ๊นŒ์š”? ์—ฌ๊ธฐ์„œ Future์™€ Task ๊ฐ์ฒด๊ฐ€ ๋“ฑ์žฅํ•ฉ๋‹ˆ๋‹ค.

# https://github.com/python/cpython/blob/main/Lib/asyncio/futures.py
class Future:
 """
 Represents the result of an asynchronous computation.
 This class is *almost* compatible with concurrent.futures.Future.
 """

Future ๊ฐ์ฒด๋Š” ์™„๋ฃŒ๋˜์—ˆ์„ ์ˆ˜๋„ ์žˆ๊ณ  ์•„๋‹ ์ˆ˜๋„ ์žˆ๋Š” ์ง€์—ฐ๋œ ๊ณ„์‚ฐ์„ ํ‘œํ˜„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋˜๋Š” ๊ฐ์ฒด์ž…๋‹ˆ๋‹ค. Future๋Š” ์ฃผ๋กœ asyncio๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฐ์ฒด๋กœ, ์šฐ๋ฆฌ๊ฐ€ ์ง์ ‘ ์ƒ์„ฑํ•˜๊ฑฐ๋‚˜ ๊ด€๋ฆฌํ•  ํ•„์š”๋Š” ์—†์Šต๋‹ˆ๋‹ค. ์ด๋Š” asyncio๊ฐ€ ์–ด๋–ค ์ž‘์—…์ด ์–ธ์ œ ์™„๋ฃŒ๋ ์ง€๋ฅผ ์•Œ๊ณ  ์žˆ๊ณ , ๊ทธ ํ๋ฆ„์„ ์Šค์Šค๋กœ ๊ด€๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์ž…๋‹ˆ๋‹ค. Future ๊ฐ์ฒด๋Š” .result() ๋‚˜ .add_done_callback() ๊ฐ™์€ ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ž‘์—…์˜ ์™„๋ฃŒ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•˜๊ณ  ๊ฒฐ๊ณผ๋‚˜ ์˜ˆ์™ธ๋ฅผ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์ค๋‹ˆ๋‹ค. ์ฆ‰ ์ง€์—ฐ๋œ ์ž‘์—…์„ ์บก์Аํ™”ํ•˜๋Š” ๊ฐ์ฒด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ทธ๋ ‡๋‹ค๋ฉด Future๋ฅผ ์—ฌ๋Ÿฌ ๊ฐœ ์ƒ์„ฑํ•ด์„œ ํ์— ๋‹ด์•„ ๊ด€๋ฆฌํ•˜๋ฉด ์ฝ”๋ฃจํ‹ด๋“ค์„ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์ง€ ์•Š์„๊นŒ์š”?

์‹ค์ œ๋กœ asyncio๋Š” ๊ทธ๋Ÿฐ ๋ฐฉ์‹์œผ๋กœ ์ฝ”๋ฃจํ‹ด์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด asyncio.as_completed()๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ ํ์™€ Future ๊ฐ์ฒด(์ •ํ™•ํžˆ๋Š” ์ฝ”๋ฃจํ‹ด ์‹คํ–‰์— ํŠนํ™”๋œ Future์„ ์ƒ์†๋ฐ›์€ Task ๊ฐ์ฒด)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฝ”๋ฃจํ‹ด์„ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

def as_completed(fs, *, timeout=None):
 """Return an iterator whose values are coroutines.

 When waiting for the yielded coroutines you'll get the results (or
 exceptions!) of the original Futures (or coroutines), in the order
 in which and as soon as they complete.

 This differs from PEP 3148; the proper way to use this is:

 for f in as_completed(fs):
 result = await f  # The 'await' may raise.
 # Use result.

 If a timeout is specified, the 'await' will raise
 TimeoutError when the timeout occurs before all Futures are done.

 Note: The futures 'f' are not necessarily members of fs.
 """
 if futures.isfuture(fs) or coroutines.iscoroutine(fs):
 raise TypeError(f"expect an iterable of futures, not {type(fs).__name__}")

 from .queues import Queue  # Import here to avoid circular import problem.
 done = Queue()

 loop = events._get_event_loop()
 todo = {ensure_future(f, loop=loop) for f in set(fs)}
 timeout_handle = None

 def _on_timeout():
 for f in todo:
 f.remove_done_callback(_on_completion)
 done.put_nowait(None)  # Queue a dummy value for _wait_for_one().
 todo.clear()  # Can't do todo.remove(f) in the loop.

 def _on_completion(f):
 if not todo:
 return  # _on_timeout() was here first.
 todo.remove(f)
 done.put_nowait(f)
 if not todo and timeout_handle is not None:
 timeout_handle.cancel()

 async def _wait_for_one():
 f = await done.get()
 if f is None:
 # Dummy value from _on_timeout().
 raise exceptions.TimeoutError
 return f.result()  # May raise f.exception().

 for f in todo:
 f.add_done_callback(_on_completion)
 if todo and timeout is not None:
 timeout_handle = loop.call_later(timeout, _on_timeout)
 for _ in range(len(todo)):
 yield _wait_for_one()

asyncio๋Š” ๋‚ด๋ถ€์ ์œผ๋กœ Future์™€ Task๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ฝ”๋ฃจํ‹ด์„ ์Šค์ผ€์ค„๋งํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ถ”์ ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋ฏ€๋กœ, ๋ณ„๋„์˜ ๊ตฌํ˜„ ์—†์ด๋„ ํŽธํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค :)

๋น„๋™๊ธฐ ๋„คํŠธ์›Œํฌ I/O

๊ทธ๋Ÿฐ๋ฐ ํ•จ์ˆ˜๋ฅผ ์ค‘๋‹จํ•˜๊ณ  ๋‹ค์‹œ ์‹คํ–‰ํ•˜๋Š” ๊ฒŒ ๋ญ๊ฐ€ ์ข‹์„๊นŒ์š”?

๋„คํŠธ์›Œํฌ I/O๋ฅผ ์˜ˆ๋กœ ๋“ค์–ด๋ด…์‹œ๋‹ค. CPU๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ 1ns~10ns ๋‹จ์œ„๋กœ ์—ฐ์‚ฐ์„ ์ˆ˜ํ–‰ํ•˜์ง€๋งŒ, ๋„คํŠธ์›Œํฌ ์™•๋ณต ์‹œ๊ฐ„์€ ๋ณดํ†ต ์ˆ˜ ms ๋‹จ์œ„๋กœ, ๋ฌด๋ ค ์ˆ˜์‹ญ๋งŒ ๋ฐฐ๋‚˜ ๋А๋ฆฝ๋‹ˆ๋‹ค.
์˜ˆ๋ฅผ ๋“ค์–ด http.get() ๊ฐ™์€ blocking ํ˜ธ์ถœ์€ ์‘๋‹ต์ด ๋„์ฐฉํ•  ๋•Œ๊นŒ์ง€ ํ•ด๋‹น ์“ฐ๋ ˆ๋“œ๊ฐ€ ์•„๋ฌด ์ผ๋„ ํ•˜์ง€ ๋ชปํ•œ ์ฑ„ ๋Œ€๊ธฐ ์ƒํƒœ๋กœ ๋จธ๋ฌด๋ฅด๊ฒŒ ๋˜๋ฉฐ, ๊ทธ ์‹œ๊ฐ„ ๋™์•ˆ CPU ์ž์›์€ ์‚ฌ์‹ค์ƒ ๋‚ญ๋น„๋ฉ๋‹ˆ๋‹ค.

์ด ์ž‘์—…์„ ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์–ด๋–จ๊นŒ์š”?
I/O ์š”์ฒญ ์ดํ›„ ์ฝ”๋ฃจํ‹ด์˜ ์‹คํ–‰์„ ์ผ์‹œ ์ค‘๋‹จ(suspend)ํ•˜๊ณ , ๊ทธ์‚ฌ์ด์— ๋‹ค๋ฅธ ์ž‘์—…์„ ๋จผ์ € ์ˆ˜ํ–‰ํ•œ ๋’ค, ์‘๋‹ต์ด ๋„์ฐฉํ•˜๋ฉด ์žฌ๊ฐœ(resume)ํ•˜๋„๋ก ๊ตฌ์„ฑํ•˜๋ฉด, CPU๋Š” ๋Œ€๊ธฐ ์‹œ๊ฐ„ ๋™์•ˆ์—๋„ ์œ ์šฉํ•œ ์ž‘์—…์„ ๊ณ„์† ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

์‹œ์Šคํ…œ์ฝœ๊ณผ DMA

๊ทธ๋ ‡๋‹ค๋ฉด ์ด๋Ÿฐ ์ค‘๋‹จ๊ณผ ์žฌ๊ฐœ๊ฐ€ ์–ด๋–ป๊ฒŒ ๊ฐ€๋Šฅํ• ๊นŒ์š”?

์šด์˜์ฒด์ œ ์ˆ˜์—…์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋– ์˜ฌ๋ ค ๋ณด๋ฉด, ๋ฐ”๋กœ DMA ๊ฐœ๋…์ด ์ƒ๊ฐ๋‚ฉ๋‹ˆ๋‹ค.
๋„คํŠธ์›Œํฌ ๋ฐ์ดํ„ฐ๋ฅผ ์†ก์ˆ˜์‹ ํ•  ๋•Œ๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ NIC๊ฐ€ DMA๋ฅผ ํ†ตํ•ด ์ง์ ‘ ๋ฉ”๋ชจ๋ฆฌ์— ์ ‘๊ทผํ•ฉ๋‹ˆ๋‹ค. ์ด ๊ณผ์ •์—๋Š” CPU์˜ ๊ฐœ์ž…์ด ๊ฑฐ์˜ ํ•„์š” ์—†์Šต๋‹ˆ๋‹ค.
NIC๋Š” ์ „์†ก์ด ๋๋‚˜๋ฉด ์ธํ„ฐ๋ŸฝํŠธ(interrupt)๋ฅผ ํ†ตํ•ด CPU์— ์•Œ๋ฆฌ๊ณ , ์ดํ›„ CPU๋Š” ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
์ฆ‰, CPU๋Š” ๋„คํŠธ์›Œํฌ I/O๊ฐ€ ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ํ•„์š” ์—†์ด ๋‹ค๋ฅธ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

epoll๊ณผ ์‹œ์Šคํ…œ ์ฝœ์˜ ์—ญํ• 

๊ทธ๋ ‡๋‹ค๋ฉด CPU๋Š” ์–ด๋–ป๊ฒŒ "๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ–ˆ๋‹ค"๋Š” ์‚ฌ์‹ค์„ ์•Œ๊ฒŒ ๋ ๊นŒ์š”?
์ด๋•Œ ๋“ฑ์žฅํ•˜๋Š” ๊ฒƒ์ด ๋ฐ”๋กœ OS์˜ ์‹œ์Šคํ…œ ์ฝœ์ž…๋‹ˆ๋‹ค.
์šด์˜์ฒด์ œ๋Š” select, poll, epoll ๊ฐ™์€ ์‹œ์Šคํ…œ ์ฝœ์„ ํ†ตํ•ด ์—ด์–ด๋‘” ์†Œ์ผ“์ด๋‚˜ ํŒŒ์ผ ๋””์Šคํฌ๋ฆฝํ„ฐ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ–ˆ๋Š”์ง€๋ฅผ ๊ฐ์‹œํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ์ด ์‹œ์Šคํ…œ ์ฝœ๋“ค์„ ํ†ตํ•ด "์ฝ์„ ์ค€๋น„๊ฐ€ ๋œ ์†Œ์ผ“"์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

select, poll, epoll ๋“ฑ์ด ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด, ๊ฐ ์‹œ์Šคํ…œ ์ฝœ์— ๋Œ€ํ•œ man ํŽ˜์ด์ง€๋‚˜ ๊ด€๋ จ ๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด ๋ณด๋Š” ๊ฒƒ์„ ์ถ”์ฒœํ•ฉ๋‹ˆ๋‹ค.

asyncio์™€ Selector

Python์˜ asyncio๋„ ํ•ด๋‹น ์‹œ์Šคํ…œ ์ฝœ์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก  ์ด ์‹œ์Šคํ…œ ์ฝœ์„ ์ง์ ‘ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ , ๋‚ด๋ถ€์ ์œผ๋กœ Selector ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์ถ”์ƒํ™”ํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

# https://github.com/python/cpython/blob/2d037fb406fd8662862c5da40a23033690235f1d/Lib/asyncio/unix_events.py#L57
class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
 """Unix event loop.

 Adds signal handling and UNIX Domain Socket support to SelectorEventLoop.
 """

CPython/Lib/selectors.py๋ฅผ ๋ณด๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ํ˜„์žฌ ํ”Œ๋žซํผ์—์„œ ๊ฐ€์žฅ ํšจ์œจ์ ์ธ ์‹œ์Šคํ…œ ์ฝœ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค.

# https://github.com/python/cpython/blob/2d037fb406fd8662862c5da40a23033690235f1d/Lib/selectors.py#L609
# Choose the best implementation, roughly:#    epoll|kqueue|devpoll > poll > select.
# select() also can't accept a FD > FD_SETSIZE (usually around 1024)
if _can_use('kqueue'):
 DefaultSelector = KqueueSelector
elif _can_use('epoll'):
 DefaultSelector = EpollSelector
elif _can_use('devpoll'):
 DefaultSelector = DevpollSelector
elif _can_use('poll'):
 DefaultSelector = PollSelector
else:
 DefaultSelector = SelectSelector

์‹ค์ œ ๋น„๊ตํ•ด ๋ณด๊ธฐ

์ด์ œ ๋™๊ธฐ์™€ ๋น„๋™๊ธฐ ๋ฐฉ์‹์˜ ๋„คํŠธ์›Œํฌ I/O์˜ ์†๋„ ์ฐจ์ด๋ฅผ ์ง์ ‘ ๋น„๊ตํ•ด ๋ด…์‹œ๋‹ค.
์•„๋ž˜๋Š” ๊ฐ„๋‹จํ•œ HTTPS GET ์š”์ฒญ์˜ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.

import asyncio, ssl
import socket


async def fetch_https_async(host: str):
 # ๋น„๋™๊ธฐ ์ฝ”๋“œ
 ssl_context = ssl.create_default_context()
 # 1. ๋น„๋™๊ธฐ์ ์œผ๋กœ TCP ์—ฐ๊ฒฐ + SSL ํ•ธ๋“œ์…ฐ์ดํฌ๊นŒ์ง€ ์™„๋ฃŒ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆผ
 reader, writer = await asyncio.open_connection(host, 443, ssl=ssl_context)
 request_header = (
 f"GET / HTTP/1.1\r\n" f"Host: {host}\r\n" f"Connection: close\r\n\r\n"
 )
 writer.write(request_header.encode("utf-8"))
 # 2. write ๋ฒ„ํผ๊ฐ€ ๋น„์›Œ์งˆ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ (์†Œ์ผ“์ด writable ์ƒํƒœ๊ฐ€ ๋  ๋•Œ๊นŒ์ง€)
 await writer.drain() # ์–ด์งธ์„œ ํ•จ์ˆ˜ ์ด๋ฆ„์ด drain.. ใ…  ํŒŒ์ด์ด์ฌ ์ฐธ..	# 3. ์„œ๋ฒ„ ์‘๋‹ต์ด ๋„์ฐฉํ•  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ (readable ์ƒํƒœ๋ฅผ ๊ธฐ๋‹ค๋ฆผ)
 response_data = await reader.read()
 response_data.decode("utf-8", errors="ignore")
 writer.close()
 # 4. ์—ฐ๊ฒฐ์ด ์™„์ „ํžˆ ์ข…๋ฃŒ๋˜๊ธฐ๋ฅผ ๊ธฐ๋‹ค๋ฆผ
 await writer.wait_closed()


def fetch_https_sync(host: str):
 # ๋™๊ธฐ ์ฝ”๋“œ
 ssl_context = ssl.create_default_context()

 sock = socket.create_connection((host, 443))
 ssock = ssl_context.wrap_socket(sock, server_hostname=host)
 request = f"GET / HTTP/1.1\r\n" f"Host: {host}\r\n" f"Connection: close\r\n\r\n"
 ssock.sendall(request.encode("utf-8"))

 response = b""
 while True:
 chunk = ssock.recv(4096)
 if not chunk:
 break
 response += chunk
 response.decode("utf-8", errors="ignore")
 ssock.close()
 sock.close()


import time

urls = [
 "example.com",
 "www.python.org",
 "www.google.com",
 "www.wikipedia.org",
 "www.naver.com",
 "www.daum.net",
 "www.reddit.com",
]


def sync_get():
 print("=== SYNC ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ===")
 start = time.time()
 for host in urls:
 fetch_https_sync(host)
 elapsed = time.time() - start
 print(f"๋™๊ธฐ ์ด์†Œ์š” ์‹œ๊ฐ„: {elapsed:.2f}์ดˆ\n")


async def async_get():
 print("=== ASYNC ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ===")
 start = time.time()
 tasks = [fetch_https_async(host) for host in urls]
 results = await asyncio.gather(*tasks)
 elapsed = time.time() - start
 print(f"๋น„๋™๊ธฐ ์ด์†Œ์š” ์‹œ๊ฐ„: {elapsed:.2f}์ดˆ\n")


if __name__ == "__main__":
 sync_get()
 asyncio.run(async_get())

''''
ํ•„์ž์˜ ์ปดํ“จํ„ฐ ํ™˜๊ฒฝ

=== SYNC ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ===
๋™๊ธฐ ์ด์†Œ์š” ์‹œ๊ฐ„: 2.81์ดˆ

=== ASYNC ํ…Œ์ŠคํŠธ ์‹œ์ž‘ ===
๋น„๋™๊ธฐ ์ด์†Œ์š” ์‹œ๊ฐ„: 0.88์ดˆ
'''

ํ™•์‹คํžˆ ๋น„๋™๊ธฐ ๋ฐฉ์‹์ด ๋” ๋น ๋ฆ…๋‹ˆ๋‹ค.
๋™๊ธฐ ๋ฐฉ์‹์€ ์š”์ฒญ์„ ์ˆœ์ฐจ์ ์œผ๋กœ ํ•˜๋‚˜์”ฉ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•˜๋ฏ€๋กœ, ๊ฐ ์š”์ฒญ์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๋ฌด์กฐ๊ฑด ๊ธฐ๋‹ค๋ ค์•ผ ํ•˜์ฃ .
๋ฐ˜๋ฉด ๋น„๋™๊ธฐ ๋ฐฉ์‹์—์„œ๋Š” await ํ‚ค์›Œ๋“œ๋ฅผ ๋งŒ๋‚  ๋•Œ๋งˆ๋‹ค ํ˜„์žฌ ์‹คํ–‰์„ ์ผ์‹œ ์ค‘๋‹จ(suspend)ํ•˜๊ณ , ์ œ์–ด๊ถŒ์„ ์ด๋ฒคํŠธ ๋ฃจํ”„์— ๋„˜๊น๋‹ˆ๋‹ค.
์ด๋ฒคํŠธ ๋ฃจํ”„๋Š” ๊ทธ๋™์•ˆ ๋‹ค๋ฅธ ์ค€๋น„๋œ ์ฝ”๋ฃจํ‹ด์„ ์‹คํ–‰ํ•˜๊ณ , ์ด์ „ ์ฝ”๋ฃจํ‹ด์ด ๋‹ค์‹œ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์ƒํƒœ๊ฐ€ ๋˜๋ฉด ์žฌ๊ฐœ(resume)์‹œํ‚ต๋‹ˆ๋‹ค.
์ด๋Ÿฐ ๊ตฌ์กฐ ๋•๋ถ„์— I/O์ฒ˜๋Ÿผ ๋А๋ฆฐ ์ž‘์—…์„ ๊ธฐ๋‹ค๋ฆฌ๋Š” ๋™์•ˆ CPU๋Š” ๋‹ค๋ฅธ ์œ ์šฉํ•œ ์ž‘์—…์„ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.

๊ฒฐ๋ก 

์™œ ๋น„๋™๊ธฐ ๋„คํŠธ์›Œํฌ ์ฒ˜๋ฆฌ์ฒ˜๋Ÿผ ๋ณด์ด๋Š” open_connection()์ด ๋ณ„๋„ ๋„คํŠธ์›Œํฌ ๋ชจ๋“ˆ๋กœ ๋น ์ง€์ง€ ์•Š๊ณ ,
asyncio ์•ˆ์— ๊ทธ๋Œ€๋กœ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€,
๋˜ ์™œ ์ต์ˆ™ํ•œ flush()๊ฐ€ ์•„๋‹Œ drain()์ด๋ผ๋Š” ์ด๋ฆ„์ด ์“ฐ์˜€๋Š”์ง€ ์ •๋ง ํŒŒ์ด์ฌ๋‹ค์šด ์•Œ์ญ๋‹ฌ์ญํ•œ ์˜๋ฌธ๊ณผ ํ•จ๊ป˜, ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์˜ ๊ฐœ๋…๊ณผ ๋™์ž‘ ๋ฐฉ์‹์„ ๊ฐ„๋‹จํžˆ ์งš์–ด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

๋‹ค์Œ ๊ธ€์—์„œ๋Š” asyncio๋ฅผ ํ™œ์šฉํ•œ ์‹ค์ „ ๋น„๋™๊ธฐ ํŒจํ„ด๊ณผ ์ฃผ์˜ํ•  ์ ๋“ค, ํ˜น์€ GIL์ด ์กด์žฌํ•˜๋Š” ํŒŒ์ด์ฌ ํ™˜๊ฒฝ์—์„œ ๋น„๋™๊ธฐ์™€ ๋ฉ€ํ‹ฐ ํ”„๋กœ์„ธ์‹ฑ์˜ ์ฐจ์ด ๋˜๋Š” ๋น„๋™๊ธฐ๋ฅผ ์ •๋ง ์ž˜ ์“ธ ์ˆ˜ ์žˆ๋Š” ํ˜„์‹ค์ ์ธ ์‚ฌ๋ก€ ๊ทธ๋ฆฌ๊ณ  uvloop ํ†บ์•„๋ณด๊ธฐ ์ค‘ ํ•˜๋‚˜๋ฅผ ์‚ดํŽด๋ณด๋ ค ํ•ฉ๋‹ˆ๋‹ค.

๊ธด ๊ธ€ ์ฝ์–ด์ฃผ์…”์„œ ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค.

์ถœ์ฒ˜

์ „๋ฌธ๊ฐ€๋ฅผ ์œ„ํ•œ ํŒŒ์ด์ฌ 2ํŒ
์™ธ๊ตญ ๋ธ”๋กœ๊ทธ
์™ธ๊ตญ ๋ธ”๋กœ๊ทธ

profile
๋‚ด๊ฐ€ ๋ฐฐ์šด ๊ฒƒ ์ •๋ฆฌ

0๊ฐœ์˜ ๋Œ“๊ธ€