๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Python

Asyncio - ๋น„๋™๊ธฐ ํ”„๋กœ๊ทธ๋ž˜๋ฐ

by ์„œ์•„๋ž‘๐Ÿ˜ 2024. 6. 23.

 

์ฃผ์š” ๊ฐœ๋…

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

  1. ์ด๋ฒคํŠธ ๋ฃจํ”„ (Event Loop):
    • ์ด๋ฒคํŠธ ๋ฃจํ”„๋Š” ์ฝ”๋ฃจํ‹ด๊ณผ ์ฝœ๋ฐฑ์„ ๊ด€๋ฆฌํ•˜๊ณ , I/O ์ž‘์—…์ด ์™„๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค.
    • asyncio.run() ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ ๋ฃจํ”„๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ์ฝ”๋ฃจํ‹ด (Coroutine):
    • async def๋กœ ์ •์˜๋œ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
    • await ํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์ด๋‚˜ ๋น„๋™๊ธฐ ์ž‘์—…์„ ๊ธฐ๋‹ค๋ฆฝ๋‹ˆ๋‹ค.
  3. ํƒœ์Šคํฌ (Task):
    • ์ฝ”๋ฃจํ‹ด์„ ์ด๋ฒคํŠธ ๋ฃจํ”„์—์„œ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋Š” ๋‹จ์œ„๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
    • asyncio.create_task()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ํƒœ์Šคํฌ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , ์ด๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ์ฝ”๋ฃจํ‹ด์„ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  4. ํ“จ์ฒ˜ (Future):
    • ๋น„๋™๊ธฐ ์—ฐ์‚ฐ์˜ ๊ฒฐ๊ณผ๋ฅผ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
    • ์ฝ”๋ฃจํ‹ด์ด๋‚˜ ํƒœ์Šคํฌ์˜ ๊ฒฐ๊ณผ๋ฅผ ํ‘œํ˜„ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

 

์ฃผ์š” ํ•จ์ˆ˜ ๋ฐ ์‚ฌ์šฉ๋ฒ•

์ด๋ฒคํŠธ ๋ฃจํ”„ ์‹คํ–‰

import asyncio

async def main():
    print("Hello, asyncio!")

# ์ด๋ฒคํŠธ ๋ฃจํ”„ ์‹คํ–‰
asyncio.run(main())

์ฝ”๋ฃจํ‹ด ์ •์˜ ๋ฐ ์‹คํ–‰

import asyncio

async def say_hello():
    print("Hello!")
    await asyncio.sleep(1)
    print("Goodbye!")

async def main():
    await say_hello()

asyncio.run(main())

ํƒœ์Šคํฌ ์ƒ์„ฑ ๋ฐ ์‹คํ–‰

import asyncio

async def say_after(delay, message):
    await asyncio.sleep(delay)
    print(message)

async def main():
    task1 = asyncio.create_task(say_after(1, "Hello"))
    task2 = asyncio.create_task(say_after(2, "World"))

    await task1
    await task2

asyncio.run(main())

 

 

๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

์—ฌ๋Ÿฌ ํƒœ์Šคํฌ ๋™์‹œ์— ์‹คํ–‰

import asyncio

async def say_hello():
    await asyncio.sleep(1)
    print("Hello!")

async def say_goodbye():
    await asyncio.sleep(2)
    print("Goodbye!")

async def main():
    await asyncio.gather(say_hello(), say_goodbye())

asyncio.run(main())

ํƒ€์ž„์•„์›ƒ ์„ค์ •

import asyncio

async def long_running_task():
    await asyncio.sleep(5)
    return "Completed"

async def main():
    try:
        result = await asyncio.wait_for(long_running_task(), timeout=3)
        print(result)
    except asyncio.TimeoutError:
        print("Task timed out")

asyncio.run(main())

๋น„๋™๊ธฐ I/O ์ž‘์—…

import asyncio

async def read_file(file_path):
    loop = asyncio.get_event_loop()
    with open(file_path, 'r') as file:
        data = await loop.run_in_executor(None, file.read)
    return data

async def main():
    content = await read_file('example.txt')
    print(content)

asyncio.run(main())

 

์š”์•ฝ

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

 

โœ”๏ธ์—ฌ๊ธฐ๊นŒ์ง€๊ฐ€ ์›น ํŽ˜์ด์ง€ ๋ฐ GPT์˜ ๋„์›€์„ ๋ฐ›์€ ํฌ์ŠคํŒ…์ž…๋‹ˆ๋‹ค.


โœ”๏ธ ์—ฌ๊ธฐ๋ถ€ํ„ฐ๋Š” ๊ฐ•์˜๋ฅผ ํ†ตํ•ด ํ•™์Šตํ•œ ๋‚ด์šฉ ์ •๋ฆฌ์ž…๋‹ˆ๋‹ค.

 

๊ฐœ์š”

import asyncio
import time

async def sleep():
	await asyncio.sleep(1)
	
async def sum(name, numbers):
  start = time.time()
  total = 0
    
	for number in numbers:
		await sleep()
    total += number
    print(f'์ž‘์—…์ค‘={name}, number={number}, total={total}')
  end = time.time()
  print(f'์ž‘์—…๋ช…={name}, ๊ฑธ๋ฆฐ์‹œ๊ฐ„={end-start}')
  
	return total

async def main():
  start = time.time()

	task1 = asyncio.create_task(sum("A", [1, 2]))
	task2 = asyncio.create_task(sum("B", [1, 2, 3]))
	
	await task1
	await task2
	
	result1 = task1.result()
	result2 = task2.result()

  end = time.time()
  print(f'์ดํ•ฉ={result1+result2}, ์ด์‹œ๊ฐ„={end-start}')

if __name__ == "__main__":
	asyncio.run(main())

ํ•จ์ˆ˜๋ฅผ ๋น„๋™๊ธฐ๋กœ ํ˜ธ์ถœํ•˜๋ ค๋ฉด ์ด๋ ‡๊ฒŒ def ์•ž์— async๋ผ๋Š” ํ‚ค์›Œ๋“œ๋ฅผ ๋„ฃ์œผ๋ฉด ๋œ๋‹ค. ๊ทธ๋Ÿฌ๋ฉด ์ด์ œ ์ด ํ•จ์ˆ˜๋Š” ๋น„๋™๊ธฐ ํ•จ์ˆ˜๊ฐ€ ๋œ๋‹ค. ์ด๋•Œ async๋ฅผ ์ ์šฉํ•œ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ์ฝ”๋ฃจํ‹ด์ด๋ผ ๋ถ€๋ฅธ๋‹ค.

๋˜ํ•œ, ์ฝ”๋ฃจํ‹ด ์•ˆ์—์„œ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์„ ํ˜ธ์ถœํ•  ๋•Œ๋Š” await sleep()๊ณผ ๊ฐ™์ด await๋ฅผ ํ•จ์ˆ˜๋ช… ์•ž์— ๋ถ™์—ฌ ํ˜ธ์ถœํ•ด์•ผ ํ•œ๋‹ค. ์ฝ”๋ฃจํ‹ด ์ˆ˜ํ–‰ ์ค‘ await ์ฝ”๋ฃจํ‹ด์„ ๋งŒ๋‚˜๋ฉด await๋กœ ํ˜ธ์ถœํ•œ ์ฝ”๋ฃจํ‹ด์ด ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๊ณ  ์ œ์–ด๊ถŒ์„ ๋ฉ”์ธ ์Šค๋ ˆ๋“œ๋‚˜ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ๋„˜๊ธด๋‹ค. ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์„ ๋„Œ๋ธ”๋กํ‚น(non-blocking)์ด๋ผ ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ˜ธ์ถœํ•œ ์ฝ”๋ฃจํ‹ด์ด ์ข…๋ฃŒ๋˜๋ฉด ์ด๋ฒคํŠธ์— ์˜ํ•ด ๋‹ค์‹œ ๊ทธ ์ดํ›„ ์ž‘์—…์ด ์ˆ˜ํ–‰๋œ๋‹ค.

์—ฌ๊ธฐ์„œ ํ•˜๋‚˜ ๋ˆˆ์—ฌ๊ฒจ๋ด์•ผ ํ•  ์ ์€ sleep() ํ•จ์ˆ˜์—์„œ time.sleep(1) ๋Œ€์‹  asyncio.sleep(1)๋ฅผ ์‚ฌ์šฉํ•œ ๋ถ€๋ถ„์ด๋‹ค. ์ฝ”๋ฃจํ‹ด์ด ์•„๋‹Œ time.sleep(1)์„ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด await๊ฐ€ ์ ์šฉ๋˜์ง€ ์•Š์•„ ์‹คํ–‰ ์‹œ๊ฐ„์„ ์ค„์ผ ์ˆ˜ ์—†๋‹ค.

main() ํ•จ์ˆ˜์—์„œ ์‚ฌ์šฉํ•œ asyncio.create_task()๋Š” ์ˆ˜ํ–‰ํ•  ์ฝ”๋ฃจํ‹ด ์ž‘์—…(ํƒœ์Šคํฌ)์„ ์ƒ์„ฑํ•œ๋‹ค. ์—ฌ๊ธฐ์„œ๋Š” ์ž‘์—…์„ ์ƒ์„ฑํ•  ๋ฟ์ด์ง€ ์‹ค์ œ๋กœ ์ฝ”๋ฃจํ‹ด์ด ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. ์‹ค์ œ ์ฝ”๋ฃจํ‹ด ์‹คํ–‰์€ await ํƒœ์Šคํฌ๊ฐ€ ๋‹ด๋‹นํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์‹คํ–‰ ํƒœ์Šคํฌ์˜ ๊ฒฐ๊ด๊ฐ’์€ ํƒœ์Šคํฌ.result()๋กœ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.

asyncio.create_task()๋Š” ์ฝ”๋ฃจํ‹ด์„ ๋™์‹œ์— ์‹คํ–‰ํ•˜๋Š” ๋ฐ ๊ผญ ํ•„์š”ํ•˜๋‹ค. ๋‹ค์Œ์ฒ˜๋Ÿผ ํƒœ์Šคํฌ๊ฐ€ ์•„๋‹Œ await๋กœ ์ฝ”๋ฃจํ‹ด์„ ์‹คํ–‰ํ•œ๋‹ค๋ฉด ์ฝ”๋ฃจํ‹ด์ด ๋™์‹œ์— ์‹คํ–‰๋˜์ง€ ์•Š๊ณ  ํ•˜๋‚˜์”ฉ ์ฐจ๋ก€๋กœ ์‹คํ–‰๋˜์–ด ์ด๋“์ด ์—†์„ ๊ฒƒ์ด๋‹ค.

result1 =await sum("A", [1, 2])
result2 =await sum("B", [1, 2, 3])

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

์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

c:\\projects\\pylib>python asyncio_sample.py
์ž‘์—…์ค‘=A, number=1, total=1
์ž‘์—…์ค‘=B, number=1, total=1
์ž‘์—…์ค‘=A, number=2, total=3
์ž‘์—…๋ช…=A, ๊ฑธ๋ฆฐ์‹œ๊ฐ„=2.000617742538452
์ž‘์—…์ค‘=B, number=2, total=3
์ž‘์—…์ค‘=B, number=3, total=6
์ž‘์—…๋ช…=B, ๊ฑธ๋ฆฐ์‹œ๊ฐ„=3.000927209854126
์ดํ•ฉ=9, ์ด์‹œ๊ฐ„=3.000927209854126

A ์ž‘์—…๊ณผ B ์ž‘์—…์„ ๊ต๋Œ€๋กœ ํ˜ธ์ถœํ•œ๋‹ค. (์ œ์–ด๊ถŒ์ด await์— ์˜ํ•ด ๊ณ„์† ๋ฐ”๋€๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.) ๊ทธ๋ฆฌ๊ณ  ์‹œ๊ฐ„๋„ 5์ดˆ ๊ฑธ๋ฆฌ๋˜ ๊ฒƒ์ด 3์ดˆ๋งŒ ๊ฑธ๋ฆฌ๊ฒŒ ๋˜๋ฏ€๋กœ A, B ์ž‘์—…์ด ์™„์ „ํžˆ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๋™์ž‘ํ–ˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

๋น„์Šทํ•œ ์˜ˆ์ œ

import asyncio
import time

# Sync ํ•จ์ˆ˜
def execute_calc_sync(name, n):
    for i in range(1, n+1):
        print(f"{name} -> {i} of {n} is calculating...")
        time.sleep(1)
        
    print(f"{name} is done!")

def process_sync():
    start = time.time()
    
    execute_calc_sync('one', 3)
    execute_calc_sync('two', 2)
    execute_calc_sync('three', 1)

    end = time.time()
    
    print(f"total seconds : {end - start}")

# Async ํ•จ์ˆ˜
async def execute_calc_async(name, n):
    for i in range(1, n+1):
        print(f"{name} -> {i} of {n} is calculating...")
        await asyncio.sleep(1)
        
    print(f"{name} is done!")

async def process_async():
    start = time.time()
    
    await asyncio.gather(
        execute_calc_async('one', 1),
        execute_calc_async('two', 2),
        execute_calc_async('three', 3)
    )
    
    end = time.time()
    print(f"total seconds : {end - start}")

if __name__ == "__main__":
    # Sync ์‹คํ–‰
    # process_sync()
    
    # Async ์‹คํ–‰
    # ํŒŒ์ด์ฌ 3.7 ์ด์ƒ
    asyncio.run(process_async())

one -> 1 of 1 is calculating...
two -> 1 of 2 is calculating...
three -> 1 of 3 is calculating...
one is done!
two -> 2 of 2 is calculating...
three -> 2 of 3 is calculating...
two is done!
three -> 3 of 3 is calculating...
three is done!
total seconds : 3.0082101821899414

Syncํ•จ์ˆ˜๋กœ ์‹คํ–‰ํ•˜๋ฉด 6์ดˆ๊ฐ€ ๊ฑธ๋ฆฐ๋‹ค. ์ฝ”๋“œ์—์„œ ASync ํ•จ์ˆ˜๋ถ€๋ถ„์— ์ง‘์ค‘ํ•˜์ž. main์—์„œ asyncio.runํ˜•ํƒœ๋กœ asyncํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์—ฌ๋Ÿฌ๊ฐœ์˜ ๋น„๋™๊ธฐ ํ•จ์ˆ˜๋ฅผ ๋ฌถ์–ด์„œ await์œผ๋กœ ํ˜ธ์ถœํ•  ์ˆ˜ ์žˆ๋Š” await asyncio,gather๋ฅผ ์‚ฌ์šฉํ–ˆ๋‹ค.

 

์ฐธ๊ณ ํ•  ๋งŒํ•œ ๋ฌธ์„œ

071 ๋น„๋™๊ธฐ ๋ฐฉ์‹์œผ๋กœ ํ”„๋กœ๊ทธ๋ž˜๋ฐํ•˜๋ ค๋ฉด? โ€• asyncio

ํŒŒ์ด์ฌ ์ฝ”๋”ฉ ๋„์žฅ: 47.10 asyncio ์‚ฌ์šฉํ•˜๊ธฐ

๋Œ“๊ธ€