ν”„λ‘œκ·Έλž˜λ° μ–Έμ–΄/Python

Asyncio - 비동기 ν”„λ‘œκ·Έλž˜λ°

μ„œμ•„λž‘πŸ˜ 2024. 6. 23. 23:07

 

μ£Όμš” κ°œλ…

파이썬의 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 μ‚¬μš©ν•˜κΈ°