programing

스레드화 모듈과 멀티프로세싱 모듈의 차이점은 무엇입니까?

linuxpc 2023. 6. 5. 23:40
반응형

스레드화 모듈과 멀티프로세싱 모듈의 차이점은 무엇입니까?

사용법을 배우고 있습니다.threading 리고그고.multiprocessing특정 작업을 병렬로 실행하고 내 코드를 가속화하는 Python의 모듈.

저는 (이론적 배경이 없기 때문에) 이것이 무엇이 다른지 이해하기 어렵다는 것을 알고 있습니다.threading.Thread()multiprocessing.Process() 둘, 셋

또한, 작업 대기열을 인스턴스화하고 4개(예를 들어)만 병렬로 실행하는 반면 나머지 작업은 실행되기 전에 리소스가 확보되기를 기다리는 방법이 완전히 명확하지 않습니다.

설명서에 나와 있는 예제는 명확하지만 매우 포괄적이지는 않습니다. 제가 일을 조금 복잡하게 하려고 하면 많은 이상한 오류(예: 절일 수 없는 방법 등)가 발생합니다.

그래서, 언제 사용해야 합니까?threading그리고.multiprocessing모듈?

이 두 모듈의 개념과 복잡한 작업에 적합하게 사용하는 방법을 설명하는 자료를 연결해 주시겠습니까?

Giulio Franco의 말은 일반적으로 멀티스레딩 대 멀티프로세싱해당합니다.

그러나* Python에는 다음과 같은 추가 문제가 있습니다.같은 프로세스에 있는 두 스레드가 동시에 Python 코드를 실행하지 못하도록 하는 Global Interpreter Lock이 있습니다.즉, 코어가 8개이고 스레드 8개를 사용하도록 코드를 변경하면 800% CPU를 사용하고 8배 더 빠르게 실행할 수 없습니다. 동일한 100% CPU를 사용하고 동일한 속도로 실행됩니다.(실제로는 공유 데이터가 없더라도 스레드화로 인한 추가 오버헤드가 있기 때문에 실행 속도가 약간 느려집니다. 하지만 지금은 무시하십시오.

여기에는 예외가 있습니다.만약 당신의 코드의 과도한 계산이 실제로 파이썬에서 일어나지 않는다면, 당신은 numpy 앱과 같이 적절한 GIL 처리를 하는 사용자 지정 C 코드가 있는 일부 라이브러리에서 스레드화로부터 예상되는 성능 이점을 얻을 것입니다.실행하고 대기하는 일부 하위 프로세스에 의해 대량의 계산이 수행되는 경우에도 마찬가지입니다.

더 중요한 것은 이것이 중요하지 않은 경우가 있다는 것입니다.예를 들어, 네트워크 서버는 네트워크 밖에서 패킷을 읽는 데 대부분의 시간을 소비하고 GUI 앱은 사용자 이벤트를 기다리는 데 대부분의 시간을 소비합니다.네트워크 서버 또는 GUI 앱에서 스레드를 사용하는 한 가지 이유는 메인 스레드가 네트워크 패킷 또는 GUI 이벤트를 계속 서비스하는 것을 중단하지 않고 장시간 실행되는 "백그라운드 작업"을 수행할 수 있도록 하기 위해서입니다.그리고 이것은 파이썬 스레드에서 잘 작동합니다.(기술적으로 Python 스레드는 코어 병렬을 제공하지 않더라도 동시성을 제공합니다.)

그러나 순수 파이썬에서 CPU 바인딩 프로그램을 작성하는 경우 스레드를 더 많이 사용하는 것은 일반적으로 도움이 되지 않습니다.

각 공정마다 개별 GIL이 있기 때문에 별도의 공정을 사용하는 것은 GIL에 이러한 문제가 없습니다.물론 스레드와 프로세스 간에는 다른 언어와 마찬가지로 여전히 동일한 트레이드오프가 존재합니다. 스레드 간보다 프로세스 간에 데이터를 공유하는 것이 더 어렵고 비용이 많이 듭니다. 대량의 프로세스를 실행하거나 프로세스를 자주 생성 및 파괴하는 등의 비용이 발생할 수 있습니다.그러나 GIL은 C나 Java와 같은 경우에는 그렇지 않은 방식으로 프로세스에 대한 균형에 큰 비중을 두고 있습니다.따라서 C나 Java보다 파이썬에서 멀티프로세싱을 훨씬 더 자주 사용하는 자신을 발견할 수 있습니다.


한편, Python의 "배터리 포함" 철학은 다음과 같은 좋은 소식을 제공합니다.한 줄 변경으로 스레드와 프로세스 간에 전환할 수 있는 코드를 작성하는 것은 매우 쉽습니다.

입력 및 출력을 제외하고 다른 작업(또는 기본 프로그램)과 공유하지 않는 자체 포함 "작업" 측면에서 코드를 설계하는 경우 라이브러리를 사용하여 다음과 같은 스레드 풀 주변에 코드를 작성할 수 있습니다.

with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
    executor.submit(job, argument)
    executor.map(some_function, collection_of_independent_things)
    # ...

그 직업으로 도 있고, 등으로 ; 이러한작결나거전하달다완또작순있니료, 습수수기도로서작등할행의을는업업의 .Future자세한 내용을 확인할 수 있습니다.

프로그램이 계속해서 100% CPU를 사용하고 스레드를 더 추가하면 속도가 느려지는 것으로 판명되면 GIL 문제에 직면하게 되므로 프로세스로 전환해야 합니다.첫 번째 줄만 변경하면 됩니다.

with concurrent.futures.ProcessPoolExecutor(max_workers=4) as executor:

유일하게 주의해야 할 점은 작업의 인수와 반환 값이 피클링 가능해야 하며(피클링에 너무 많은 시간이나 메모리가 소요되지 않아야 함) 교차 프로세스를 사용할 수 있다는 것입니다.보통 이것은 문제가 되지 않지만, 가끔은 문제가 됩니다.


하지만 여러분의 직업이 자급자족할 수 없다면 어떨까요?메시지를 다른 사람에게 전달하는 작업의 측면에서 코드를 설계할 수 있다면 여전히 매우 쉽습니다.를 사용해야 할 수도 있습니다.threading.Thread또는multiprocessing.Process수영장에 의존하는 대신.그리고 당신은 창조해야 할 것입니다.queue.Queue또는multiprocessing.Queue명시적으로 객체. (파이프, 소켓, 플럭이 있는 파일 등 다양한 옵션이 있지만 중요한 것은 실행자의 자동 마법이 충분하지 않으면 수동으로 무언가를 해야 한다는 것입니다.)

하지만 만약 여러분이 메시지 전달에 의지할 수 없다면 어떻게 될까요?같은 구조를 변형시키고 서로의 변화를 보기 위해 두 개의 직업이 필요하다면 어떨까요?이 경우 수동 동기화(잠금, 세마포, 조건 등)를 수행하고 프로세스를 사용하려는 경우 부팅할 명시적 공유 메모리 개체를 수행해야 합니다.이때 멀티스레딩(또는 멀티프로세싱)이 어려워집니다.만약 피할 수 있다면, 좋습니다. 만약 피할 수 없다면, 여러분은 SO 답변에 누군가가 넣을 수 있는 것보다 더 많은 것을 읽어야 할 것입니다.


코멘트를 통해 Python의 스레드와 프로세스 간에 무엇이 다른지 알고자 했습니다.Giulio Franco씨의 답변과 저의 답변, 그리고 당사의 모든 링크를 읽어본다면 모든 것을 다룰 수 있을 것입니다. 하지만 요약은 분명히 유용할 것입니다. 따라서 다음과 같이 설명합니다.

  1. 스레드는 기본적으로 데이터를 공유하지만 프로세스는 공유하지 않습니다.
  2. (1)의 결과로 프로세스 간에 데이터를 전송하려면 일반적으로 데이터를 **피클링하고 피클링 해제해야 합니다.
  3. (의 또 으로 Value및 (1) 의른결프간스로데직로일세에이으반를합다접니및적저야, Array같로해장은를레데터형이우식과 낮은 의 형식으로 를 공유해야 .ctypestypes.
  4. 프로세스는 GIL의 적용을 받지 않습니다.
  5. 일부 플랫폼(주로 윈도우즈)에서는 프로세스를 생성하고 파괴하는 데 훨씬 더 많은 비용이 듭니다.
  6. 프로세스에는 몇 가지 추가적인 제한이 있으며, 그 중 일부는 플랫폼마다 다릅니다.자세한 내용은 프로그래밍 지침을 참조하십시오.
  7. threading모에 .multiprocessing (할 수 .)multiprocessing.dummy, 더 을 사용할 수 .concurrent.futures그리고 그것에 대해 걱정하지 마세요.)

실제로 이 문제가 있는 것은 언어인 파이썬이 아니라 해당 언어의 "표준" 구현인 Cython입니다.Jython과 같은 일부 다른 구현에는 GIL이 없습니다.

멀티프로세싱에 포크 시작 방법(대부분의 Windows 플랫폼에서 사용할 수 있음)을 사용하는 경우 각 자식 프로세스는 자식이 시작될 때 부모가 가지고 있던 모든 리소스를 얻으며, 이는 자식에게 데이터를 전달하는 또 다른 방법이 될 수 있습니다.

단일 프로세스에 여러 스레드가 존재할 수 있습니다.동일한 프로세스에 속하는 스레드는 동일한 메모리 영역을 공유합니다(매우 동일한 변수에서 읽고 쓸 수 있으며 서로 간섭할 수 있음).반대로, 서로 다른 프로세스는 서로 다른 메모리 영역에 살고 있으며, 각각의 프로세스에는 고유한 변수가 있습니다.통신하기 위해서는 프로세스가 다른 채널(파일, 파이프 또는 소켓)을 사용해야 합니다.

계산을 병렬화하려면 멀티스레드가 필요합니다. 스레드가 동일한 메모리에서 작동하기를 원할 수 있기 때문입니다.

성능에 대해 말하자면, 스레드는 프로세스보다 생성 및 관리가 빠르며(OS가 완전히 새로운 가상 메모리 영역을 할당할 필요가 없기 때문), 스레드 간 통신은 일반적으로 프로세스 간 통신보다 빠릅니다.하지만 스레드는 프로그래밍하기가 더 어렵습니다.스레드는 서로 간섭할 수 있고 서로의 메모리에 쓸 수 있지만 이러한 방식이 항상 명확한 것은 아닙니다(주로 명령 순서 변경 및 메모리 캐싱). 따라서 변수에 대한 액세스를 제어하기 위해 동기화 프리미티브가 필요합니다.

Python 문서 인용문

프로세스 대 스레드 및 GIL에 대한 Python 문서의 주요 인용문을 CPython의 글로벌 인터프리터 잠금(GIL)이란 무엇입니까?

프로세스 대 스레드 실험

저는 그 차이를 좀 더 구체적으로 보여주기 위해 약간의 벤치마킹을 했습니다.

벤치마크에서 저는 8개의 하이퍼스레딩 CPU에서 다양한 수의 스레드에 대한 CPU 및 IO 바인딩 작업 시간을 측정했습니다.스레드당 제공되는 작업은 항상 동일하므로 스레드가 많을수록 총 작업량이 증가합니다.

결과는 다음과 같습니다.

여기에 이미지 설명 입력

데이터 그림을 표시합니다.

결론:

  • CPU 바인딩된 작업의 경우 GIL로 인해 멀티프로세싱이 항상 더 빠릅니다.

  • IO 바인딩된 작업의 경우 둘 다 속도가 정확하게 동일합니다.

  • 제가 8개의 하이퍼스레딩 머신을 사용하고 있기 때문에 스레드는 예상되는 8배가 아닌 약 4배까지만 확장됩니다.

    예상 속도가 8배 빨라지는 CPOSIX CPU 바인딩 작업과 비교:시간(1)의 출력에서 '실제', '사용자' 및 'sys'는 무엇을 의미합니까?

    TODO: 저는 이것의 이유를 모르겠습니다. 다른 파이썬 비효율성이 작용하고 있음에 틀림없습니다.

테스트 코드:

#!/usr/bin/env python3

import multiprocessing
import threading
import time
import sys

def cpu_func(result, niters):
    '''
    A useless CPU bound function.
    '''
    for i in range(niters):
        result = (result * result * i + 2 * result * i * i + 3) % 10000000
    return result

class CpuThread(threading.Thread):
    def __init__(self, niters):
        super().__init__()
        self.niters = niters
        self.result = 1
    def run(self):
        self.result = cpu_func(self.result, self.niters)

class CpuProcess(multiprocessing.Process):
    def __init__(self, niters):
        super().__init__()
        self.niters = niters
        self.result = 1
    def run(self):
        self.result = cpu_func(self.result, self.niters)

class IoThread(threading.Thread):
    def __init__(self, sleep):
        super().__init__()
        self.sleep = sleep
        self.result = self.sleep
    def run(self):
        time.sleep(self.sleep)

class IoProcess(multiprocessing.Process):
    def __init__(self, sleep):
        super().__init__()
        self.sleep = sleep
        self.result = self.sleep
    def run(self):
        time.sleep(self.sleep)

if __name__ == '__main__':
    cpu_n_iters = int(sys.argv[1])
    sleep = 1
    cpu_count = multiprocessing.cpu_count()
    input_params = [
        (CpuThread, cpu_n_iters),
        (CpuProcess, cpu_n_iters),
        (IoThread, sleep),
        (IoProcess, sleep),
    ]
    header = ['nthreads']
    for thread_class, _ in input_params:
        header.append(thread_class.__name__)
    print(' '.join(header))
    for nthreads in range(1, 2 * cpu_count):
        results = [nthreads]
        for thread_class, work_size in input_params:
            start_time = time.time()
            threads = []
            for i in range(nthreads):
                thread = thread_class(work_size)
                threads.append(thread)
                thread.start()
            for i, thread in enumerate(threads):
                thread.join()
            results.append(time.time() - start_time)
        print(' '.join('{:.6e}'.format(result) for result in results))

GitHub 업스트림 + 동일한 디렉토리에 코드를 표시합니다.

Ubuntu 18.10, Python 3.6.7, CPU가 장착된 Lenovo ThinkPad P51 노트북에서 테스트:인텔 Core i7-7820HQ CPU(4개 코어/8개 스레드), RAM: Samsung M471A2K43BB1-CRC(2x16Gb), SSD: Samsung MZVLB512HAJQ-000L7(3,000MB/s).

특정 시간에 실행 중인 스레드 시각화

게시물 https://rohanvarma.me/GIL/ 은 다음과 같은 인수를 가진 스레드가 예약될 때마다 콜백을 실행할 수 있다고 알려주었습니다.multiprocessing.Process.

이를 통해 각 시간에 실행되는 스레드를 정확하게 볼 수 있습니다.이 작업이 완료되면 다음과 같은 결과가 나타납니다(이 그래프를 작성했습니다).

            +--------------------------------------+
            + Active threads / processes           +
+-----------+--------------------------------------+
|Thread   1 |********     ************             |
|         2 |        *****            *************|
+-----------+--------------------------------------+
|Process  1 |***  ************** ******  ****      |
|         2 |** **** ****** ** ********* **********|
+-----------+--------------------------------------+
            + Time -->                             +
            +--------------------------------------+

이는 다음을 보여줍니다.

  • 스레드는 GIL에 의해 완전히 직렬화됩니다.
  • 프로세스를 병렬로 실행할 수 있음

는 이 링크가 당신의 질문에 우아하게 대답한다고 믿습니다.

즉, 하위 문제 중 하나가 완료되는 동안 다른 문제가 완료될 때까지 기다려야 하는 경우 멀티스레딩이 좋습니다(예: I/O 작업이 많은 경우). 반대로 하위 문제가 실제로 동시에 발생할 수 있는 경우 멀티프로세싱이 권장됩니다.그러나 코어 수보다 더 많은 프로세스를 생성하지는 않습니다.

다음은 IO 바인딩 시나리오에서 멀티프로세싱보다 스레드화가 더 성능이 좋다는 개념에 의문을 제기하는 python 2.6.x의 일부 성능 데이터입니다.이 결과는 40-프로세서 IBM System x3650 M4 BD에서 얻은 것입니다.

IO 바인딩된 처리: 프로세스 풀의 성능이 스레드 풀보다 우수함

>>> do_work(50, 300, 'thread','fileio')
do_work function took 455.752 ms

>>> do_work(50, 300, 'process','fileio')
do_work function took 319.279 ms

CPU 바인딩된 처리: 프로세스 풀의 성능이 스레드 풀보다 우수함

>>> do_work(50, 2000, 'thread','square')
do_work function took 338.309 ms

>>> do_work(50, 2000, 'process','square')
do_work function took 287.488 ms

이것들은 엄격한 테스트는 아니지만 멀티프로세싱이 스레드화에 비해 완전히 성능이 떨어지는 것은 아니라는 것을 알려줍니다.

위의 테스트를 위해 대화형 파이썬 콘솔에 사용된 코드

from multiprocessing import Pool
from multiprocessing.pool import ThreadPool
import time
import sys
import os
from glob import glob

text_for_test = str(range(1,100000))

def fileio(i):
 try :
  os.remove(glob('./test/test-*'))
 except : 
  pass
 f=open('./test/test-'+str(i),'a')
 f.write(text_for_test)
 f.close()
 f=open('./test/test-'+str(i),'r')
 text = f.read()
 f.close()


def square(i):
 return i*i

def timing(f):
 def wrap(*args):
  time1 = time.time()
  ret = f(*args)
  time2 = time.time()
  print '%s function took %0.3f ms' % (f.func_name, (time2-time1)*1000.0)
  return ret
 return wrap

result = None

@timing
def do_work(process_count, items, process_type, method) :
 pool = None
 if process_type == 'process' :
  pool = Pool(processes=process_count)
 else :
  pool = ThreadPool(processes=process_count)
 if method == 'square' : 
  multiple_results = [pool.apply_async(square,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]
 else :
  multiple_results = [pool.apply_async(fileio,(a,)) for a in range(1,items)]
  result = [res.get()  for res in multiple_results]


do_work(50, 300, 'thread','fileio')
do_work(50, 300, 'process','fileio')

do_work(50, 2000, 'thread','square')
do_work(50, 2000, 'process','square')

대부분의 질문은 줄리아오 프랑코가 답했습니다.저는 소비자-생산자 문제에 대해 더 자세히 설명할 것입니다. 멀티스레드 앱 사용에 대한 당신의 솔루션이 올바른 방향으로 나아갈 수 있을 것으로 생각합니다.

fill_count = Semaphore(0) # items produced
empty_count = Semaphore(BUFFER_SIZE) # remaining space
buffer = Buffer()

def producer(fill_count, empty_count, buffer):
    while True:
        item = produceItem()
        empty_count.down();
        buffer.push(item)
        fill_count.up()

def consumer(fill_count, empty_count, buffer):
    while True:
        fill_count.down()
        item = buffer.pop()
        empty_count.up()
        consume_item(item)

동기화 프리미티브에 대한 자세한 내용은 다음에서 확인할 수 있습니다.

 http://linux.die.net/man/7/sem_overview
 http://docs.python.org/2/library/threading.html

의사 코드는 위에 있습니다.생산자-소비자-문제를 검색해서 더 많은 참고 자료를 얻어야 할 것 같습니다.

언급URL : https://stackoverflow.com/questions/18114285/what-are-the-differences-between-the-threading-and-multiprocessing-modules

반응형