programing

Python 3의 멀티 프로세싱과 멀티스레딩의 비동기화

sourcejob 2023. 1. 27. 21:18
반응형

Python 3의 멀티 프로세싱과 멀티스레딩의 비동기화

Python 3.4에서는 멀티프로세싱/스레딩을 위한 라이브러리가 거의 없다는 것을 알게 되었습니다.멀티프로세싱 vs 스레드화 vs 비동기화입니다.

하지만 어떤 것을 사용해야 할지, 아니면 '추천하는 것'인지 모르겠습니다.그들은 같은 일을 하나요, 아니면 다른가요?그렇다면 어떤 것이 무엇에 사용됩니까?컴퓨터에 멀티코어를 사용하는 프로그램을 만들고 싶어요.하지만 어떤 도서관을 배워야 할지 모르겠어.

TL;DR

올바른 선택:

우리는 가장 인기 있는 동시성의 형태를 살펴보았다.그러나 문제는 남아 있다. 언제 어느 쪽을 선택해야 하는가?사용 사례에 따라 다릅니다.내 경험(및 읽기)으로 미루어 볼 때, 나는 다음과 같은 유사 코드를 따르는 경향이 있다.

if io_bound:
    if io_very_slow:
        print("Use Asyncio")
    else:
        print("Use Threads")
else:
    print("Multi Processing")
  • CPU 바인딩 => 다중 처리
  • I/O 바운드, 고속 I/O, 제한된 연결 수 => 멀티 스레드화
  • I/O 바인딩, 느린 I/O, 많은 연결 => 비동기식

언급


[주의] :

  • 긴 콜 방식(sleeve time이나 lazy I/O를 포함한 방식 등)을 사용하고 있는 경우는 단일 스레드를 동시성으로 동작하는 비동기 방식, 트위스트 방식 또는 토네이도 방식(coroutine 방식)이 최적입니다.
  • asyncio는 Python 3.4 이상에서 작동합니다.
  • Python 2.7 이후 Twisted와 Twisted가 준비되었습니다.
  • uvloop은 초고속입니다.asyncioevent loop(uv loop의 경우)asyncio2~4시)입니다.

[UPDATE(2019년)] :

  • Japranto는 uvloop에 기반한 매우 빠른 파이프라인 HTTP 서버입니다.

이러한 용도는 (약간) 다른 목적 및/또는 요건이 다릅니다.CPython(일반적인 메인라인 Python 구현)은 여전히 글로벌 인터프리터 잠금을 가지고 있기 때문에 멀티 스레드 애플리케이션(현재 병렬 처리를 구현하는 표준 방법)은 최적이라고 할 수 없습니다.그래서...multiprocessing 보다 바람직할 수 있다threading그러나 모든 문제가 효과적으로 [거의 독립된] 조각으로 분할되는 것은 아니기 때문에 프로세스 간 커뮤니케이션이 많이 필요할 수 있습니다.★★★★★★★★★★★★★★★★★★.multiprocessing 않을 수도 threading일반적으로

asyncio(이 기술은 Python에서만 사용할 수 있는 것이 아니라 Boost와 같은 다른 언어 및/또는 프레임워크에서도 사용할 수 있습니다.ASIO)는 병렬 코드 실행이 필요 없는 여러 소스로부터의 많은 I/O 작업을 효과적으로 처리하는 방법입니다.따라서 이는 특정 작업에 대한 솔루션일 뿐이지 일반적으로 병렬 처리를 위한 솔루션일 수는 없습니다.

다중 처리에서는 여러 CPU를 활용하여 계산을 분산합니다.각 CPU는 병렬로 실행되므로 여러 작업을 동시에 실행할 수 있습니다.CPU에 바인드된 태스크에는 멀티프로세싱을 사용할 수 있습니다.예를 들어, 거대한 목록의 모든 요소의 합계를 계산하려고 하는 경우가 있습니다.컴퓨터에 8개의 코어가 있는 경우 목록을 8개의 작은 목록으로 "잘라내기"하고 각 목록의 합계를 개별 코어로 계산한 후 이들 숫자를 합산할 수 있습니다.이렇게 하면 속도가 최대 8배 향상됩니다.

(멀티) 스레드에서는 여러 CPU가 필요하지 않습니다.웹에 많은 HTTP 요청을 보내는 프로그램을 상상해 보십시오.단일 스레드 프로그램을 사용하는 경우 각 요청에서 실행(차단)을 중지하고 응답을 기다린 후 응답을 받으면 계속 진행합니다.여기서의 문제는 외부 서버가 작업을 수행하기를 기다리는 동안 CPU가 제대로 작동하지 않는다는 것입니다.그 사이에 실제로 도움이 될 수도 있습니다.수정 방법은 스레드를 사용하는 것입니다. 스레드 중 많은 스레드를 만들 수 있으며, 각 스레드는 웹에서 일부 콘텐츠를 요청하는 역할을 합니다.스레드의 장점은 한 CPU에서 실행되더라도 CPU는 때때로 한 스레드의 실행을 "동결"시키고 다른 스레드의 실행으로 넘어간다는 것입니다(이를 컨텍스트 스위칭이라고 하며 결정적이지 않은 간격으로 항상 발생합니다).따라서 작업이 I/O바인드인 경우 스레드를 사용합니다.

asyncio는 기본적으로 CPU가 아니라 프로그래머(또는 실제로 어플리케이션)로서 컨텍스트스위치가 발생하는 장소와 타이밍을 결정하는 스레드화입니다.Python에서는,await(「coroutine」을 사용하여 ).async키워드를 지정합니다.

기본적인 생각은 다음과 같습니다.

IO-BOUND? -----------------------> USE 인가요?asyncio

CPU 사용률이 높은가? --------------------------------------------------------multiprocessing

그렇지 않으면? --------------------------------------------------------------

따라서 기본적으로 IO/CPU 문제가 없는 한 스레드화를 계속하십시오.

많은 답변이 하나의 옵션만 선택하는 방법을 제안하고 있는데, 이 세 가지를 모두 사용할 수 없는 이유는 무엇입니까?에서는 어떻게 할 수 하겠습니다.asyncio대신 3가지 형태의 동시성을 모두 조합하여 관리하고 필요에 따라 나중에 쉽게 교환할 수 있습니다.

단답


은 Python을 하게 될 입니다.processing.Process ★★★★★★★★★★★★★★★★★」threading.Thread은 API가 하는 높은 concurrent.futures있기 때문에 예 중 가 되고 있습니다.또, 산란 프로세스나 스레드에는, 메모리 증설등의 오버헤드가 있어, 이하에 나타내는 몇개의 예를 들 수 있습니다.concurrent.futures님은 이를 관리하여 수천 개의 프로세스를 생성하거나 컴퓨터를 크래시하는 등의 작업을 쉽게 수행할 수 없습니다.단, 몇 개의 프로세스만 산란하고 종료할 때마다 이러한 프로세스를 재사용하는 것만으로 처리됩니다.

높은 는 API를 제공됩니다.concurrent.futures.Executor에 (에 의해) concurrent.futures.ProcessPoolExecutor ★★★★★★★★★★★★★★★★★」concurrent.futures.ThreadPoolExecutor. 의 경우,이는 "" "" " " " " " " " " 에 multiprocessing.Process ★★★★★★★★★★★★★★★★★」threading.Thread를 사용하면, 장래에 다른 것으로 변경하기 쉬워지기 때문입니다.concurrent.futures각각의 세부 차이점을 배울 필요가 없습니다.

은 통합 에 이 할 수 multiprocessing ★★★★★★★★★★★★★★★★★」threading 사용하다concurrent.futuresasyncio는 아니고의 코드로 할 수 .다음 코드를 사용하여 사용할 수 있습니다.

import asyncio
from concurrent.futures import Executor
from functools import partial
from typing import Any, Callable, Optional, TypeVar

T = TypeVar("T")

async def run_in_executor(
    executor: Optional[Executor],
    func: Callable[..., T],
    /,
    *args: Any,
    **kwargs: Any,
) -> T:
    """
    Run `func(*args, **kwargs)` asynchronously, using an executor.

    If the executor is None, use the default ThreadPoolExecutor.
    """
    return await asyncio.get_running_loop().run_in_executor(
        executor,
        partial(func, *args, **kwargs),
    )

# Example usage for running `print` in a thread.
async def main():
    await run_in_executor(None, print, "O" * 100_000)

asyncio.run(main())

'우리'를 사용하는 은 '우리'를 사용하면threadingasyncio너무 흔해서 Python 3.9에서asyncio.to_thread(func, *args, **kwargs)로 단축하면ThreadPoolExecutor.

긴 답변


이 접근법에 단점이 있습니까?

아, 아, 아, 아, 아, 아, 아, 네.asyncio가장 큰 단점은 비동기 함수가 동기 함수와 같지 않다는 것입니다.로 인해 새로운 수 .asyncio로 프로그래밍을 시작하지 않으면 많은 작업을 할 수 있습니다.asyncio이치노

다른 은 코드 도 강제로 입니다.asyncio은 종종 됩니다.asyncio시큼한 맛을 가진 사용자들

퍼포먼스 이외의 이점이 있습니까?

아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, 아, concurrent.futuresthreading.Thread ★★★★★★★★★★★★★★★★★」multiprocessing.Process 이 는, 보다 인 것으로 할 수 .Executor비동기 함수로 변환합니다. '하다'를 사용해서 시작할 수 요.asyncio에 그 되면 '필요'가 있습니다threading ★★★★★★★★★★★★★★★★★」multiprocessing , 을 사용하면 .asyncio.to_thread ★★★★★★★★★★★★★★★★★」run_in_executor존재함을 될 수 스레딩으로 실행하려는 버전은 쉽게 할 수 .따라서 사용에서 쉽게 물러날 수 있습니다.threading으로 전환합니다.asyncio★★★★★★ 。

퍼포먼스상의 이점이 있습니까?

안 돼요.궁극적으로 그것은 작업에 달려있다.경우에 따라서는 도움이 되지 않을 수도 있고(다치지 않을 수도 있지만), 큰 도움이 될 수도 있습니다.에서는 '왜'를 사용하는지에 하겠습니다.asyncio Executor유리할 수 있습니다.

- 여러 실행자 및 기타 비동기 코드 결합

asyncio는 기본적으로 동시성을 훨씬 더 많이 제어할 수 있지만 동시성을 더 많이 제어할 필요가 있습니다.를 사용하여 일부 코드를 동시에 실행하는 경우ThreadPoolExecutor ProcessPoolExecutor하는 것은 만, 이 코드에서는 매우 asyncio.

import asyncio
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

async def with_processing():
    with ProcessPoolExecutor() as executor:
        tasks = [...]
        for task in asyncio.as_completed(tasks):
            result = await task
            ...

async def with_threading():
    with ThreadPoolExecutor() as executor:
        tasks = [...]
        for task in asyncio.as_completed(tasks):
            result = await task
            ...

async def main():
    await asyncio.gather(with_processing(), with_threading())

asyncio.run(main())

이게 어떻게 작동하나요?본질적으로asyncio실행자에게 기능을 실행하도록 요구합니다.그 후 때 " " " 가 됩니다.asyncio다른 코드를 실행합니다.를 들면, 「」는,ProcessPoolExecutor가 완료될 하는 동안 에러가 합니다.ThreadPoolExecutor여러 가닥의 실타래가 시작되죠. asyncio그런 다음 실행자를 체크하고 완료되면 결과를 수집합니다. 다른 를 사용하고 경우는, 이 코드를 사용해 주세요.asyncio프로세스와 스레드가 완료될 때까지 기다리면서 실행할 수 있습니다.

- 실행자가 필요한 코드 섹션 좁히기

코드에 실행자가 많은 것은 흔치 않지만, 스레드/프로세스를 사용할 때 공통적으로 볼 수 있는 문제는 코드 전체를 스레드/프로세스에 삽입하여 동작하도록 하는 것입니다.예를 들어, 다음 코드(약)를 본 적이 있습니다.

from concurrent.futures import ThreadPoolExecutor
import requests

def get_data(url):
    return requests.get(url).json()["data"]

urls = [...]

with ThreadPoolExecutor() as executor:
    for data in executor.map(get_data, urls):
        print(data)

은 이 이 더 왜일까요? ★★★★★★★★★★★★★★★★★★★★★★★★★★★★.json많은 스레드가 대량의 메모리를 소비하는 것은 큰 문제가 됩니다.다행히 해결책은 간단했습니다.

from concurrent.futures import ThreadPoolExecutor
import requests

urls = [...]

with ThreadPoolExecutor() as executor:
    for response in executor.map(requests.get, urls):
        print(response.json()["data"])

json한 번에 메모리에 언로드되어 모든 것이 정상입니다.

여기서의 교훈?

모든 코드를 스레드/프로세스에 슬랩하려고 하지 말고 코드의 어떤 부분이 실제로 동시성이 필요한지에 초점을 맞춰야 합니다.

하지만 만약get_data이 사건처럼 간단한 기능이 아니었을까요?실행자를 기능의 중간 어딘가에 적용해야 한다면 어떨까요?여기가 바로 그 장소입니다asyncio★★★★★★★★★★★★★★★★★★:

import asyncio
import requests

async def get_data(url):
    # A lot of code.
    ...
    # The specific part that needs threading.
    response = await asyncio.to_thread(requests.get, url, some_other_params)
    # A lot of code.
    ...
    return data

urls = [...]

async def main():
    tasks = [get_data(url) for url in urls]
    for task in asyncio.as_completed(tasks):
        data = await task
        print(data)

asyncio.run(main())

방법으로 시도하다concurrent.futures결코 예쁘지 않다.할 수 , 인 콜백이나 큐보다 asyncio

이미 좋은 답변들이 많네요.각각의 사용 시기에 대해 더 자세히 설명할 수 없습니다.이것은 둘의 흥미로운 조합이다.멀티프로세싱 + 비동기 : https://pypi.org/project/aiomultiprocess/

설계 시 사용 사례는 하이오였지만 여전히 사용 가능한 코어를 많이 사용하고 있습니다.페이스북은 이 라이브러리를 사용하여 파이썬 기반의 파일 서버를 작성했습니다.I/O 바인드트래픽을 허가하는 비동기 기능.다만, 복수의 코어로 복수의 이벤트 루프와 스레드를 허가하는 멀티 프로세싱 기능.

리포의 Ex 코드:

import asyncio
from aiohttp import request
from aiomultiprocess import Pool

async def get(url):
    async with request("GET", url) as response:
        return await response.text("utf-8")

async def main():
    urls = ["https://jreese.sh", ...]
    async with Pool() as pool:
        async for result in pool.map(get, urls):
            ...  # process result
            
if __name__ == '__main__':
    # Python 3.7
    asyncio.run(main())
    
    # Python 3.6
    # loop = asyncio.get_event_loop()
    # loop.run_until_complete(main())

여기에 덧붙이자면 노트북은 이미 비동기 루프가 실행 중이기 때문에 주피터 노트북에서는 잘 작동하지 않습니다.머리카락을 뽑지 않도록 주의해 주세요.

언급URL : https://stackoverflow.com/questions/27435284/multiprocessing-vs-multithreading-vs-asyncio-in-python-3

반응형