asyncio vs thread - goddes4/python-study-wiki GitHub Wiki
๋ค์์ concurrent task ๊ฐ ์กด์ฌ ํ ๋ thread ์ asyncio ์ ์ฑ๋ฅ ํ ์คํธ
- ์ ์ ์กฐ๊ฑด :
- cpu-bound task ๊ฐ ์๋ i/o bound task ์ฌ์ผ ํจ
- cpu bound task ๋ผ๋ฉด multiprocessing ๊ณ ๋ ค
task 100๊ฐ ๊ฒฐ๊ณผ
- thread : 5.033 seconds
- asyncio : 5.013 seconds
task 400๊ฐ ๊ฒฐ๊ณผ
- thread : 5.105 seconds
- asyncio : 5.045 seconds
task 800๊ฐ ๊ฒฐ๊ณผ
- thread : 5.202 seconds
- asyncio : 5.079 seconds
๊ฒฐ๋ก :
- ๋์์ฑ ์ฒ๋ฆฌ๋ฅผ ์ํด์ task ๋น thread ๋ฅผ ํ ๋น ํ๋๊ฒ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ ๋ฐ ์ปจํ ์คํธ ์ค์์น ๋น์ฉ์ด ์ฆ๊ฐ ํ๋ ๋ฌธ์ ๊ฐ ๋ฐ์.
- C10K ๋ฌธ์ ์ ๊ฒฝ์ฐ asyncio ๊ฐ ์ข์ ์๋ฃจ์ ์ด ๋ ์ ์์.
- asyncio ์ ๊ฒฝ์ฐ ์ฑ๊ธ ์ฐ๋ ๋๋ก event loop ๊ฐ task ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์.
- asyncio ๋ ์ฑ๊ธ ์ฐ๋ ๋์ด๊ธฐ ๋๋ฌธ์ task ์ blocking ์ฝ๋๊ฐ ์์ผ๋ฉด ์ ์ฒด์ ์ผ๋ก ์ฑ๋ฅ ์ ํ๊ฐ ์ค๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ์ฝ๋๋ฅผ ๋น๋๊ธฐํ ์์ผ์ผ ํจ. (using
yield from coroutine) - asyncio ์ ์ฅ์ ์ Coroutine ์ ํตํด ๋น๋๊ธฐ์ ์ฝ๋๋ฅผ ๋ง์น ๋๊ธฐ์ ์ฝ๋์ฒ๋ผ ์์ฑ ํ ์ ์์ด์ ์ฝ๋ ๊ฐ๋ ์ฑ๋ ์ข์.
- ๋ณดํต ๋น๋๊ธฐ ํธ์ถ์ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ๊ธฐ ์ํด์ callback ์ ๋ฑ๋กํ์ฌ ์ฒ๋ฆฌ ํ๊ฑฐ๋, queue ๋ฅผ ํตํด์ ์์ ๋ฐ์ ์ฒ๋ฆฌ ํ๋๋ฐ ์ด๋ฐ ์ฝ๋๊ฐ ๋ง์ ์ง๋ฉด ๊ฐ๋ ์ฑ์ด ์ ํ๋๋ค. ์ด๋ฅธ๋ฐ callback hell.
- ๊ฐ๋ฐ์๊ฐ ์ง์ I/O Multiplexing (select, poll, epoll, kqueue) ์ฝ๋๋ฅผ ๊ตฌํํ๋ ๊ฒ ๋์ด๋๊ฐ ์๋นํ๋ค
example : thread
import asyncio
from concurrent.futures.thread import ThreadPoolExecutor
from logging import DEBUG
import random
import threading
import datetime
import logging
import time
MAX_TASKS = 800
def long_time_process(task_id):
"""
์๋ฅผ ๋ค๋ฉด ์ธ๋ถ open_api ๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ ์์ง
:param task_id:
:return:
"""
print(datetime.datetime.now(), threading.current_thread(), "long_time_process start.", task_id)
# time.sleep(random.randint(1, 5))
time.sleep(5)
return "done"
def slow_task(task_id):
ret = long_time_process(task_id)
print(datetime.datetime.now(), threading.current_thread(), "slow_task-", task_id, "ret=", ret)
if __name__ == '__main__':
start_time = time.time()
with ThreadPoolExecutor(max_workers=MAX_TASKS) as executor:
for i in range(MAX_TASKS):
# task ์คํ
executor.submit(slow_task, task_id=i)
process_time = time.time() - start_time
print("process time : {}".format(process_time))
example : asyncio
import asyncio
from logging import DEBUG
import threading
import datetime
import logging
import time
MAX_TASKS = 800
@asyncio.coroutine
def long_time_process(task_id):
"""
์๋ฅผ ๋ค๋ฉด ์ธ๋ถ open_api ๋ฅผ ์ด์ฉํ์ฌ ๋ฐ์ดํฐ ์์ง
:param task_id:
:return:
"""
print(datetime.datetime.now(), threading.current_thread(), "long_time_process start.", task_id)
# yield from asyncio.sleep(random.randint(1, 5))
yield from asyncio.sleep(5)
return "done"
@asyncio.coroutine
def slow_task(task_id):
ret = yield from long_time_process(task_id)
print(datetime.datetime.now(), threading.current_thread(), "slow_task-", task_id, "ret=", ret)
logging.basicConfig(level=DEBUG)
if __name__ == '__main__':
event_loop = asyncio.get_event_loop()
# event_loop.set_debug(True)
futures = []
start_time = time.time()
for i in range(MAX_TASKS):
# task ๋ฑ๋ก : coroutine function ๋ง ๋ฑ๋ก ๊ฐ๋ฅ (coroutine decorating)
f = event_loop.create_task(slow_task(task_id=i))
futures.append(f)
# ์ต๋ 5์ด๊ฐ ๋ฑ๋ก๋ task ์ ์ข
๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.
event_loop.run_until_complete(asyncio.wait(futures, timeout=5))
event_loop.close()
process_time = time.time() - start_time
print("process time : {}".format(process_time))