Python의 동시성 측면의 차이점

Marion Paul Kenneth Mendoza 2023년6월21일
Python의 동시성 측면의 차이점

Python 3의 릴리스와 함께 비동기 작업 및 동시성에 관한 많은 새로운 추세를 듣고 있다는 점을 감안할 때 Python이 이러한 개념이나 기능을 방금 도입했다고 상상할 수 있습니다.

많은 초보자는 asyncio를 사용하는 것이 동시 및 비동기 활동을 수행하는 유일한 실용적인 접근 방식이라고 생각할 수 있습니다. 이 기사에서는 Python에서 동시성을 달성하는 방법과 이점 또는 단점에 대해 설명합니다.

스레드 및 멀티스레딩

스레드는 아주 오랫동안 파이썬에 있었습니다. 결과적으로 스레드 덕분에 한 번에 여러 작업을 수행할 수 있습니다.

유감스럽게도 전형적인 메인라인 Python 버전인 CPython은 여전히 글로벌 인터프리터 잠금(GIL)을 사용합니다. 이는 오늘날 병렬 처리를 구현하는 일반적인 방법인 다중 스레드 응용 프로그램을 이상적이지 않게 만듭니다.

Python은 GIL을 도입하여 CPython의 메모리 처리를 C와 보다 관리하기 쉽게 통합(예: 확장)할 수 있도록 했습니다.

GIL은 Python 인터프리터가 동시에 하나의 스레드만 실행한다는 점에서 잠금 메커니즘입니다. Python의 byte 코드는 한 스레드에서만 동시에 실행할 수 있습니다.

예제 코드:

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를 기다리는 작업을 일시 중지합니다.

결과적으로 우리는 미완성 프로젝트에 시간을 낭비하지 않습니다.

예제 코드:

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 기능은 다른 async 기능이 완료될 때까지 기다릴 수 있습니다.
  • 함수를 이벤트 루프에 전달합니다(ensure_future 함수 사용).
  • 이벤트 루프 실행을 시작합니다.

await 호출이 이루어질 때마다 asyncio는 함수에 약간의 시간이 필요할 것임을 이해합니다. asyncio가 정지된 함수의 I/O가 준비되었음을 확인하면 프로세스를 다시 시작합니다.

이제 요점은 세 가지 형태의 동시성 중 무엇을 사용해야 하느냐입니다. 의사 결정에 도움이 되도록 다음 사항을 참고할 수 있습니다.

  • CPU 바운드 작업에 다중 처리를 사용합니다.
  • I/O Bound, Fast I/O 및 제한된 수의 연결에 멀티스레딩을 사용합니다.
  • I/O Bound, Slow 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")
Marion Paul Kenneth Mendoza avatar Marion Paul Kenneth Mendoza avatar

Marion specializes in anything Microsoft-related and always tries to work and apply code in an IT infrastructure.

LinkedIn

관련 문장 - Python Threading

관련 문장 - Python Async