Programing

다중 처리 vs 스레딩 Python

lottogame 2020. 9. 30. 08:39
반응형

다중 처리 vs 스레딩 Python


나는의 장점을 이해하려고 노력하고 멀티 프로세싱 을 통해 스레딩 . 다중 처리 가 Global Interpreter Lock을 우회 한다는 것을 알고 있지만 다른 이점이 있으며 스레딩 이 똑같은 일을 할 수 없습니까?


threading모듈을 사용하는 스레드는 multiprocessing모듈은 프로세스를 사용합니다. 차이점은 스레드는 동일한 메모리 공간에서 실행되는 반면 프로세스에는 별도의 메모리가 있다는 것입니다. 따라서 다중 처리를 사용하여 프로세스간에 개체를 공유하기가 조금 더 어려워집니다. 스레드는 동일한 메모리를 사용하기 때문에 예방 조치를 취해야합니다. 그렇지 않으면 두 개의 스레드가 동시에 동일한 메모리에 기록됩니다. 이것이 글로벌 인터프리터 잠금의 목적입니다.

생성 프로세스는 스레드 생성보다 약간 느립니다. 일단 실행되면 큰 차이가 없습니다.


여기에 내가 생각 해낸 몇 가지 장단점이 있습니다.

다중 처리

장점

  • 별도의 메모리 공간
  • 코드는 일반적으로 간단합니다.
  • 다중 CPU 및 코어 활용
  • cPython에 대한 GIL 제한 방지
  • 공유 메모리를 사용하지 않는 한 동기화 기본 요소에 대한 대부분의 요구를 제거합니다 (대신 IPC 용 통신 모델에 가깝습니다).
  • 하위 프로세스가 중단 가능 / 종료 가능
  • Python multiprocessing모듈에는 다음과 같은 인터페이스와 함께 유용한 추상화가 포함되어 있습니다.threading.Thread
  • CPU 바운드 처리를위한 cPython 필수

단점

  • 더 많은 오버 헤드로 IPC가 조금 더 복잡합니다 (통신 모델 대 공유 메모리 / 객체).
  • 더 큰 메모리 공간

스레딩

장점

  • 경량-적은 메모리 공간
  • 공유 메모리-다른 컨텍스트에서 상태에 쉽게 액세스 할 수 있습니다.
  • 반응 형 UI를 쉽게 만들 수 있습니다.
  • GIL을 올바르게 릴리스하는 cPython C 확장 모듈은 병렬로 실행됩니다.
  • I / O 바인딩 애플리케이션을위한 훌륭한 옵션

단점

  • cPython-GIL에 따라
  • 중단 / 죽일 수 없음
  • 명령 대기열 / 메시지 펌프 모델 ( Queue모듈 사용)을 따르지 않는 경우 동기화 기본 요소를 수동으로 사용하는 것이 필요합니다 (잠금 세분화를 위해 결정이 필요함).
  • 코드는 일반적으로 이해하고 올바르게 이해하기가 더 어렵습니다. 경쟁 조건의 가능성이 극적으로 증가합니다.

스레딩의 역할은 응용 프로그램이 응답 할 수 있도록하는 것입니다. 데이터베이스 연결이 있고 사용자 입력에 응답해야한다고 가정합니다. 스레딩이 없으면 데이터베이스 연결이 사용 중이면 응용 프로그램이 사용자에게 응답 할 수 없습니다. 데이터베이스 연결을 별도의 스레드로 분리하여 애플리케이션의 응답 성을 높일 수 있습니다. 또한 두 스레드가 동일한 프로세스에 있기 때문에 동일한 데이터 구조에 액세스 할 수 있습니다 (좋은 성능과 유연한 소프트웨어 설계).

GIL로 인해 앱이 실제로 한 번에 두 가지 작업을 수행하는 것은 아니지만, 우리가 한 일은 데이터베이스의 리소스 잠금을 별도의 스레드에 배치하여 CPU 시간이 사용자 상호 작용과 전환 될 수 있도록하는 것입니다. CPU 시간은 스레드간에 배분됩니다.

멀티 프로세싱은 주어진 시간에 한 가지 이상의 작업을 수행하고 싶은 경우를위한 것입니다. 애플리케이션이 6 개의 데이터베이스에 연결하고 각 데이터 세트에서 복잡한 행렬 변환을 수행해야한다고 가정합니다. 각 작업을 별도의 스레드에 배치하면 한 연결이 유휴 상태 일 때 다른 연결이 CPU 시간을 얻을 수 있기 때문에 약간 도움이 될 수 있지만 GIL은 한 CPU의 리소스 만 사용한다는 것을 의미하기 때문에 처리가 병렬로 수행되지 않기 때문입니다. . 각 작업을 다중 처리 프로세스에 배치하면 각 작업이 자체 CPU에서 실행되고 최대 효율성으로 실행될 수 있습니다.


주요 이점은 격리입니다. 충돌하는 프로세스는 다른 프로세스를 중단시키지 않지만 충돌하는 스레드는 다른 스레드와 혼란을 일으킬 수 있습니다.


언급되지 않은 또 다른 점은 속도와 관련하여 사용중인 OS에 따라 다르다는 것입니다. Windows에서 프로세스는 비용이 많이 들기 때문에 스레드는 Windows에서 더 좋을 수 있지만 유닉스에서는 프로세스가 Windows 변형보다 빠르기 때문에 유닉스에서 프로세스를 사용하는 것이 훨씬 안전하고 빠르게 생성됩니다.


Python 문서 인용문

Process vs Threads 및 GIL에 대한 주요 Python 문서 인용문을 다음에서 강조했습니다. CPython의 GIL (Global Interpreter Lock)이란?

프로세스 대 스레드 실험

차이점을보다 구체적으로 보여주기 위해 약간의 벤치마킹을했습니다.

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

결과는 다음과 같습니다.

여기에 이미지 설명 입력

데이터를 플로팅합니다 .

결론 :

  • CPU 바운드 작업의 경우 다중 처리가 항상 더 빠릅니다. 아마도 GIL로 인해

  • IO 바인딩 작업의 경우. 둘 다 정확히 같은 속도

  • 스레드는 8 하이퍼 스레드 머신을 사용하고 있기 때문에 예상되는 8x 대신 약 4x까지만 확장됩니다.

    예상되는 8 배 속도 향상에 도달하는 C POSIX CPU 바인딩 작업과 대조됩니다. 시간 (1)의 출력에서 ​​'실제', '사용자'및 'sys'는 무엇을 의미합니까?

    TODO : 그 이유를 모르겠습니다. 다른 Python 비 효율성이 작용해야합니다.

테스트 코드 :

#!/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 업스트림 + 동일한 디렉토리에 코드 플로팅 .

Tested on Ubuntu 18.10, Python 3.6.7, in a Lenovo ThinkPad P51 laptop with CPU: Intel Core i7-7820HQ CPU (4 cores / 8 threads), RAM: 2x Samsung M471A2K43BB1-CRC (2x 16GiB), SSD: Samsung MZVLB512HAJQ-000L7 (3,000 MB/s).

Visualize which threads are running at a given time

This post https://rohanvarma.me/GIL/ taught me that you can run a callback whenever a thread is scheduled with the target= argument of threading.Thread and the same for multiprocessing.Process.

This allows us to view exactly which thread runs at each time. When this is done, we would see something like (I made this particular graph up):

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

which would show that:

  • threads are fully serialized by the GIL
  • processes can run in parallel

Other answers have focused more on the multithreading vs multiprocessing aspect, but in python Global Interpreter Lock (GIL) has to be taken into account. When more number (say k) of threads are created, generally they will not increase the performance by k times, as it will still be running as a single threaded application. GIL is a global lock which locks everything out and allows only single thread execution utilizing only a single core. The performance does increase in places where C extensions like numpy, Network, I/O are being used, where a lot of background work is done and GIL is released.
So when threading is used, there is only a single operating system level thread while python creates pseudo-threads which are completely managed by threading itself but are essentially running as a single process. Preemption takes place between these pseudo threads. If the CPU runs at maximum capacity, you may want to switch to multiprocessing.
Now in case of self-contained instances of execution, you can instead opt for pool. But in case of overlapping data, where you may want processes communicating you should use multiprocessing.Process.


As mentioned in the question, Multiprocessing in Python is the only real way to achieve true parallelism. Multithreading cannot achieve this because the GIL prevents threads from running in parallel.

As a consequence, threading may not always be useful in Python, and in fact, may even result in worse performance depending on what you are trying to achieve. For example, if you are performing a CPU-bound task such as decompressing gzip files or 3D-rendering (anything CPU intensive) then threading may actually hinder your performance rather than help. In such a case, you would want to use Multiprocessing as only this method actually runs in parallel and will help distribute the weight of the task at hand. There could be some overhead to this since Multiprocessing involves copying the memory of a script into each subprocess which may cause issues for larger-sized applications.

However, Multithreading becomes useful when your task is IO-bound. For example, if most of your task involves waiting on API-calls, you would use Multithreading because why not start up another request in another thread while you wait, rather than have your CPU sit idly by.

TL;DR

  • Multithreading is concurrent and is used for IO-bound tasks
  • Multiprocessing achieves true parallelism and is used for CPU-bound tasks

MULTIPROCESSING

  • Multiprocessing adds CPUs to increase computing power.
  • Multiple processes are executed concurrently.
  • Creation of a process is time-consuming and resource intensive.
  • Multiprocessing can be symmetric or asymmetric.
  • The multiprocessing library in Python uses separate memory space, multiple CPU cores, bypasses GIL limitations in CPython, child processes are killable (ex. function calls in program) and is much easier to use.
  • Some caveats of the module are a larger memory footprint and IPC’s a little more complicated with more overhead.

MULTITHREADING

  • Multithreading creates multiple threads of a single process to increase computing power.
  • Multiple threads of a single process are executed concurrently.
  • Creation of a thread is economical in both sense time and resource.
  • The multithreading library is lightweight, shares memory, responsible for responsive UI and is used well for I/O bound applications.
  • The module isn’t killable and is subject to the GIL.
  • Multiple threads live in the same process in the same space, each thread will do a specific task, have its own code, own stack memory, instruction pointer, and share heap memory.
  • If a thread has a memory leak it can damage the other threads and parent process.

Example of Multi-threading and Multiprocessing using Python

Python 3 has the facility of Launching parallel tasks. This makes our work easier.

It has for thread pooling and Process pooling.

The following gives an insight:

ThreadPoolExecutor Example

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

ProcessPoolExecutor

import concurrent.futures
import math

PRIMES = [
    112272535095293,
    112582705942171,
    112272535095293,
    115280095190773,
    115797848077099,
    1099726899285419]

def is_prime(n):
    if n % 2 == 0:
        return False

    sqrt_n = int(math.floor(math.sqrt(n)))
    for i in range(3, sqrt_n + 1, 2):
        if n % i == 0:
            return False
    return True

def main():
    with concurrent.futures.ProcessPoolExecutor() as executor:
        for number, prime in zip(PRIMES, executor.map(is_prime, PRIMES)):
            print('%d is prime: %s' % (number, prime))

if __name__ == '__main__':
    main()

Process may have multiple threads. These threads may share memory and are the units of execution within a process.

Processes run on the CPU, so threads are residing under each process. Processes are individual entities which run independently. If you want to share data or state between each process, you may use a memory-storage tool such as Cache(redis, memcache), Files, or a Database.


내가 대학에서 배운 것처럼 위의 대부분의 대답은 옳습니다. 다른 플랫폼 (항상 파이썬 사용)에서 PRACTICE에서 여러 스레드를 생성하면 하나의 프로세스를 생성하는 것처럼 끝납니다. 차이점은 하나의 코어 만 100 %로 모든 것을 처리하는 대신 여러 코어가 부하를 공유한다는 것입니다. 예를 들어 4 코어 PC에서 10 개의 스레드를 생성하면 결국 CPU 파워의 25 % 만 얻을 수 있습니다 !! 그리고 만약 u가 10 개의 프로세스를 생성한다면 u는 100 %의 CPU 처리로 끝날 것입니다 (다른 제한이 없다면). 나는 모든 신기술의 전문가가 아닙니다. 자신의 실제 경험 배경으로 대답하는 임

참고 URL : https://stackoverflow.com/questions/3044580/multiprocessing-vs-threading-python

반응형