Python이 이러한 개념이나 기능을 이제 막 도입했다고 생각할 수도 있습니다. Python 3의 출시와 함께 비동기 작업 및 동시성의 새로운 추세에 대해 많이 들었기 때문입니다.
많은 초보자는 asyncio를 사용하는 것이 동시 및 비동기 활동을 수행하는 유일한 실용적인 방법이라고 생각할 수 있습니다. 이 기사에서는 Python에서 동시성을 달성하는 방법과 이점 또는 단점에 대해 설명합니다.
스레드 및 멀티스레딩
스레드는 파이썬에서 오랫동안 사용되어 왔습니다. 따라서 스레드의 존재로 인해 동시에 여러 작업을 수행할 수 있습니다.
불행하게도 Python의 일반적인 메인라인 버전인 CPython은 여전히 GIL(Global Interpreter Lock)을 사용합니다. 이는 오늘날 병렬 처리를 달성하는 일반적인 방법인 멀티스레드 애플리케이션을 차선책으로 만듭니다.
Python은 C(예: 확장)와의 통합을 위해 CPython의 메모리 처리를 보다 쉽게 관리할 수 있도록 GIL을 도입했습니다.
GIL은 잠금 메커니즘입니다. 즉, Python 인터프리터는 한 번에 하나의 스레드만 실행할 수 있습니다. Python의 바이트 코드는 한 번에 하나의 스레드에서만 실행할 수 있습니다.
샘플 코드:
import threading
import time
import random
def worker(num):
sec = random.randrange(1, 5)
time.sleep(sec)
print("I am thread {}, who slept for {} seconds.".format(num, sec))
for i in range(3):
t = threading.Thread(target=worker, args=(i,))
t.start()
print("Completed!")
산출:
Completed!
I am thread 1, who slept for 3 seconds.
I am thread 3, who slept for 2 seconds.
I am thread 4, who slept for 4 seconds.
프로세스 및 다중 프로세스
다중 처리는 많은 CPU를 사용합니다. 각 CPU가 병렬로 실행되기 때문에 동시에 여러 작업을 효율적으로 실행할 수 있습니다. 이러한 CPU 바운드 작업의 경우 다중 처리를 사용하려는 것입니다.
Python은 병렬화를 달성하기 위해 multiprocessing 모듈을 도입했습니다. 스레드를 사용해 본 적이 있다면 매우 유사하게 느껴질 것입니다.
샘플 코드:
import multiprocessing
import time
import random
def worker(num):
sec = random.randrange(1, 5)
time.sleep(sec)
print("I am process {}, who slept for {} seconds.".format(num, sec))
for i in range(3):
t = multiprocessing.Process(target=worker, args=(i,))
t.start()
print("Completed")
산출:
Completed
I am process 1, who slept for 1 seconds.
I am process 2, who slept for 2 seconds.
I am process 0, who slept for 3 seconds.
멀티스레딩 대신 CPU의 서로 다른 코어에서 여러 프로세스를 실행하면 Python 스크립트가 더 빨라집니다.
비동기 및 asyncio
동기식 작업에서 작업은 차례로 동기식으로 수행됩니다. 그러나 비동기 작업에서는 작업이 서로 완전히 독립적으로 시작될 수 있습니다.
실행이 다른 활동으로 전환되는 동안 비동기 작업이 시작되고 계속 실행될 수 있습니다. 반면에 비동기 작업은 차단하지 않고 백그라운드에서 실행되는 경우가 많습니다(실행자가 완료될 때까지 기다리게 함).
다른 중요한 기능 중에서 asyncio는 이벤트 루프를 제공합니다. 이벤트 루프는 다양한 I/O 이벤트를 모니터링하고 준비된 작업으로 전환하며 I/O를 기다리는 작업을 일시 중단합니다.
따라서 우리는 미완성 프로젝트에 시간을 낭비하지 않습니다.
샘플 코드:
#Python小白学习交流群:711312441
import asyncio
import datetime
import random
async def my_sleep_func():
await asyncio.sleep(random.randint(0, 5))
async def displayDate(num, loop):
endTime = loop.time() + 60.0
while True:
print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
if (loop.time() + 1.0) >= endTime:
break
await my_sleep_func()
loop = asyncio.get_event_loop()
asyncio.ensure_future(displayDate(1, loop))
asyncio.ensure_future(displayDate(2, loop))
loop.run_forever()
위의 코드 스니펫을 살펴보면 다음과 같습니다.
- 숫자와 이벤트 루프를 매개 변수로 사용하는 비동기 함수 displayDate가 있습니다.
- 위의 함수에는 60초 후에 중지되는 무한 루프가 있습니다. 그러나 그 60초 동안 반복적으로 시간을 인쇄하고 스누즈합니다.
- await 함수는 다른 비동기 함수가 완료될 때까지 기다릴 수 있습니다.
- 이 함수를 이벤트 루프에 전달합니다(sure_future 함수 사용).
- 이벤트 루프 실행을 시작합니다.
await가 호출될 때마다 asyncio는 함수가 시간이 걸릴 수 있음을 이해합니다. asyncio는 I/O가 중지된 기능에 대해 준비되었음을 알게 되면 프로세스를 다시 시작합니다.
이제 문제는 이 세 가지 형태의 동시성 중 어떤 것을 사용해야 하느냐입니다. 결정하는 데 도움이 되는 다음 사항을 확인할 수 있습니다.
- CPU 바인딩 작업에 다중 처리를 사용합니다.
- I/O Bound, 빠른 I/O 및 제한된 연결에 멀티스레딩을 사용합니다.
- I/O 바운드, 느린 I/O 및 많은 연결의 경우 비동기 IO를 사용하십시오.
- asyncio/await는 Python 3.5 이상에서 작동합니다.
다음 의사 코드를 참조할 수도 있습니다.
if io_bound:
if io_very_slow:
print("Use asyncio")
else:
print("Use multithreading")
else:
print("multiprocessing")