해결해야 할 큰 문제가 있는데 혼자 있다고 상상해 보세요. 8개의 서로 다른 숫자의 제곱근을 계산해야 합니다. 너 뭐하니? 선택의 여지가 별로 없습니다. 첫 번째 숫자부터 시작하여 결과를 계산합니다. 그런 다음 다른 사람들에게로 넘어갑니다.
수학을 잘하고 기꺼이 도와줄 친구 세 명이 있다면 어떨까요? 그들 각각은 두 숫자의 제곱근을 계산할 것이며 작업량이 친구들에게 균등하게 분배되기 때문에 작업이 더 쉬워질 것입니다. 이는 문제가 더 빨리 해결될 것임을 의미합니다.
알겠습니다. 모든 것이 명확해졌나요? 이 예에서 각 친구는 CPU의 코어를 나타냅니다. 첫 번째 예에서는 전체 작업이 순차적으로 해결됩니다. 이것을 직렬 계산이라고 합니다. 두 번째 예에서는 총 4개의 코어를 사용하므로 병렬 컴퓨팅을 사용하고 있습니다. 병렬 컴퓨팅에는 병렬 프로세스 또는 프로세서의 여러 코어로 나누어진 프로세스를 사용하는 것이 포함됩니다.
병렬 프로그래밍이 무엇인지 알아보았는데 어떻게 사용하나요? 앞서 병렬 컴퓨팅에는 프로세서의 여러 코어에서 여러 작업을 실행하는 작업이 포함된다고 말씀드렸는데, 이는 이러한 작업이 동시에 실행된다는 의미입니다. 병렬화를 진행하기 전에 몇 가지 문제를 고려해야 합니다. 예를 들어, 계산 속도를 높일 수 있는 다른 최적화가 있습니까?
이제 병렬화가 가장 적합한 솔루션이라고 당연하게 생각합시다. 병렬 컴퓨팅에는 세 가지 주요 모드가 있습니다:
완전 병렬. 작업은 독립적으로 실행될 수 있으며 서로 통신할 필요가 없습니다.
공유 메모리 병렬 처리. 프로세스(또는 스레드)는 통신해야 하므로 전역 주소 공간을 공유합니다.
메시징. 프로세스는 필요할 때 메시지를 공유해야 합니다.
이 기사에서는 가장 간단한 첫 번째 모델에 대해 설명하겠습니다.
Python에서 병렬 처리를 달성하는 한 가지 방법은 multiprocessing 모듈을 사용하는 것입니다. multiprocessing
모듈을 사용하면 각각 자체 Python 인터프리터가 있는 여러 프로세스를 만들 수 있습니다. 따라서 Python 다중 처리는 프로세스 기반 병렬성을 구현합니다. multiprocessing
模块允许你创建多个进程,每个进程都有自己的 Python 解释器。因此,Python 多进程实现了基于进程的并行。
你可能听说过其他库,比如threading
,它也是Python内置的,但它们之间有着重要的区别。multiprocessing
模块创建新进程,而threading
Python에도 내장되어 있는 스레딩
과 같은 다른 라이브러리에 대해 들어보셨을 수도 있지만 두 라이브러리 사이에는 중요한 차이점이 있습니다. multiprocessing
모듈은 새로운 프로세스를 생성하고, threading
은 새로운 스레드를 생성합니다.
"왜 다중 처리를 선택합니까?"라고 물으실 수 있습니다. 다중 처리는 여러 작업을 순차적이 아닌 병렬로 실행하여 프로그램의 효율성을 크게 향상시킬 수 있습니다. 비슷한 용어는 멀티스레딩이지만 서로 다릅니다.
프로세스는 실행하기 위해 메모리에 로드된 프로그램이며 다른 프로세스와 메모리를 공유하지 않습니다. 스레드는 프로세스의 실행 단위입니다. 여러 스레드가 하나의 프로세스에서 실행되며 프로세스의 메모리 공간을 서로 공유합니다.
Python의 GIL(Global Interpreter Lock)은 인터프리터에서 한 번에 하나의 스레드만 실행할 수 있도록 허용합니다. 즉, Python 인터프리터가 필요한 경우 멀티스레딩의 성능 이점을 누릴 수 없습니다. 이것이 Python의 스레딩보다 다중 처리가 더 유리한 이유입니다. 각 프로세스에는 할당된 명령을 실행하는 자체 인터프리터가 있으므로 여러 프로세스가 병렬로 실행될 수 있습니다. 또한 운영 체제는 여러 프로세스에서 프로그램을 살펴보고 별도로 예약합니다. 즉, 프로그램이 전체 컴퓨터 리소스에서 더 큰 점유율을 차지하게 됩니다. 따라서 프로그램이 CPU 바인딩되면 다중 처리가 더 빠릅니다. 프로그램에 I/O가 많은 상황에서는 대부분의 경우 프로그램이 I/O가 완료되기를 기다리고 있으므로 스레드가 더 효율적일 수 있습니다. 그러나 여러 프로세스는 동시에 실행되기 때문에 일반적으로 더 효율적입니다.
다중 처리의 이점은 다음과 같습니다.
CPU 집약적인 작업을 처리할 때 CPU를 더 효율적으로 사용합니다.
스레드에 비해 하위 스레드에 대한 더 많은 제어
코드 작성이 용이함
첫 번째 장점은 성능과 관련이 있습니다. 멀티프로세싱은 새로운 프로세스를 생성하므로 작업을 다른 코어로 나누어 CPU의 컴퓨팅 성능을 더 잘 활용할 수 있습니다. 요즘 대부분의 프로세서는 멀티 코어이며, 코드를 최적화하면 병렬 컴퓨팅을 통해 시간을 절약할 수 있습니다.
두 번째 장점은 멀티스레딩의 대안입니다. 스레드는 프로세스가 아니므로 그에 따른 결과가 발생합니다. 스레드를 생성한 경우 일반 프로세스처럼 종료하거나 중단하는 것은 위험합니다. 멀티프로세싱과 멀티스레딩의 비교는 이 글의 범위를 벗어나므로, 멀티프로세싱과 멀티스레딩의 차이점에 대해서는 나중에 별도의 글을 작성하여 다루겠습니다.
멀티 프로세싱의 세 번째 장점은 처리하려는 작업이 병렬 프로그래밍에 적합하기 때문에 구현이 쉽다는 것입니다.
드디어 Python 코드를 작성할 준비가 되었습니다!
Python 다중 처리의 핵심 측면을 설명하는 데 사용할 매우 기본적인 예제부터 시작하겠습니다. 이 예에서는
parent
라는 두 개의 프로세스가 있습니다. 상위 프로세스는 하나만 있고 하위 프로세스는 여러 개 있을 수 있습니다. parent
经常。只有一个父进程,它可以有多个子进程。
child
进程。这是由父进程产生的。每个子进程也可以有新的子进程。
我们将使用该child
过程来执行某个函数。这样,parent
可以继续执行。
这是我们将用于此示例的代码:
from multiprocessing import Process def bubble_sort(array): check = True while check == True: check = False for i in range(0, len(array)-1): if array[i] > array[i+1]: check = True temp = array[i] array[i] = array[i+1] array[i+1] = temp print("Array sorted: ", array) if __name__ == '__main__': p = Process(target=bubble_sort, args=([1,9,4,5,2,6,8,4],)) p.start() p.join()
在这个片段中,我们定义了一个名为bubble_sort(array)
。这个函数是冒泡排序算法的一个非常简单的实现。如果你不知道它是什么,请不要担心,因为它并不重要。要知道的关键是它是一个可以实现某个功能的函数。
从multiprocessing
,我们导入类Process
。此类表示将在单独进程中运行的活动。事实上,你可以看到我们已经传递了一些参数:
target=bubble_sort
,意味着我们的新进程将运行该bubble_sort
函数
args=([1,9,4,52,6,8,4],)
,这是作为参数传递给目标函数的数组
一旦我们创建了 Process 类的实例,我们只需要启动该进程。这是通过编写p.start()
完成的。此时,该进程开始。
在我们退出之前,我们需要等待子进程完成它的计算。该join()
方法等待进程终止。
在这个例子中,我们只创建了一个子进程。正如你可能猜到的,我们可以通过在Process
类中创建更多实例来创建更多子进程。
如果我们需要创建多个进程来处理更多 CPU 密集型任务怎么办?我们是否总是需要明确地开始并等待终止?这里的解决方案是使用Pool
类。
Pool
类允许你创建一个工作进程池,在下面的示例中,我们将研究如何使用它。这是我们的新示例:
from multiprocessing import Pool import time import math N = 5000000 def cube(x): return math.sqrt(x) if __name__ == "__main__": with Pool() as pool: result = pool.map(cube, range(10,N)) print("Program finished!")
在这个代码片段中,我们有一个cube(x)
函数,它只接受一个整数并返回它的平方根。很简单,对吧?
然后,我们创建一个Pool
类的实例,而不指定任何属性。默认情况下,Pool
类为每个 CPU 核心创建一个进程。接下来,我们使用几个参数运行map
方法。
map
方法将cube
函数应用于我们提供的可迭代对象的每个元素——在本例中,它是从10
到N
的每个数字的列表。
这样做的最大优点是列表上的计算是并行进行的!
包joblib
是一组使并行计算更容易的工具。它是一个用于多进程的通用第三方库。它还提供缓存和序列化功能。要安装joblib
包,请在终端中使用以下命令:
pip install joblib
我们可以将之前的示例转换为以下示例以供使用joblib
:
from joblib import Parallel, delayed def cube(x): return x**3 start_time = time.perf_counter() result = Parallel(n_jobs=3)(delayed(cube)(i) for i in range(1,1000)) finish_time = time.perf_counter() print(f"Program finished in {finish_time-start_time} seconds") print(result)
事实上,直观地看到它的作用。delayed()
하위
프로세스. 이는 상위 프로세스에 의해 생성됩니다. 각 하위 프로세스에는 새로운 하위 프로세스가 있을 수도 있습니다. 🎜함수를 실행하기 위해 child
프로시저를 사용하겠습니다. 이런 방식으로 parent
가 계속 실행될 수 있습니다. 🎜
이것이 이 예에 사용할 것입니다. 코드: 🎜
result = Parallel(n_jobs=3)((cube, (i,), {}) for i in range(1,1000))
이 코드 조각에서는 bubble_sort(array)
라는 클래스를 정의합니다. 이 함수는 버블 정렬 알고리즘을 매우 간단하게 구현한 것입니다. 그것이 무엇인지 모른다면 중요하지 않으니 걱정하지 마세요. 알아야 할 중요한 점은 이것이 무언가를 수행하는 함수라는 것입니다. 🎜
다중 처리
에서 클래스를 가져옵니다프로세스. 이 클래스는 별도의 프로세스에서 실행되는 활동을 나타냅니다. 실제로 🎜
target=bubble_sort
라는 몇 가지 매개변수를 전달한 것을 볼 수 있습니다. 새로운 프로세스는 bubble_sort
함수🎜🎜🎜args=([1,9,4,52,6,8,4],)
를 실행합니다. 🎜Process 클래스의 인스턴스를 생성한 후에는 프로세스를 시작하기만 하면 됩니다. . 이는 p.start()
를 작성하여 수행됩니다. 이 시점에서 프로세스가 시작됩니다. 🎜
종료하기 전에 하위 프로세스가 계산을 완료할 때까지 기다려야 합니다. join()
메서드는 프로세스가 종료될 때까지 기다립니다. 🎜
이 예에서는 하위 프로세스를 하나만 생성합니다. 짐작할 수 있듯이 Process
클래스에 더 많은 인스턴스를 생성하여 더 많은 하위 프로세스를 생성할 수 있습니다. 🎜
CPU 집약적인 처리를 위해 여러 프로세스를 생성해야 하는 경우 유형 작업과 관련이 있습니까? 항상 명시적으로 시작하고 종료를 기다려야 합니까? 여기서 해결 방법은 Pool
클래스를 사용하는 것입니다. 🎜
Pool
클래스를 사용하면 작업자 프로세스 풀을 만들 수 있습니다. 다음 예에서는 사용 방법을 살펴보겠습니다. 다음은 새로운 예입니다. 🎜
result = Parallel(n_jobs=3, prefer="threads")(delayed(cube)(i) for i in range(1,1000))
이 코드 조각에는 정수를 받아들이고 그 제곱근을 반환하는 cube(x)
함수가 있습니다. 아주 간단하죠? 🎜
그런 다음 속성을 지정하지 않고 Pool
클래스의 인스턴스를 생성합니다. 기본적으로 Pool
클래스는 CPU 코어당 하나의 프로세스를 생성합니다. 다음으로 몇 가지 매개변수를 사용하여 map
메서드를 실행합니다. 🎜
map
메소드는 우리가 제공하는 iterable의 각 요소에 cube
함수를 적용합니다. 이 경우에는 10
부터 N
까지의 모든 숫자 목록입니다. 🎜
이것의 가장 큰 장점은 목록의 계산이 병렬로 수행된다는 것입니다! 🎜
패키지 joblib
는 병렬 컴퓨팅을 만드는 도구 세트입니다. 더 쉬운 도구. 다중 프로세스를 위한 범용 타사 라이브러리입니다. 또한 캐싱 및 직렬화 기능도 제공합니다. joblib
패키지를 설치하려면 터미널에서 다음 명령을 사용하세요. 🎜
from multiprocessing import Pool import time import math N = 5000000 def cube(x): return math.sqrt(x) if __name__ == "__main__": # first way, using multiprocessing start_time = time.perf_counter() with Pool() as pool: result = pool.map(cube, range(10,N)) finish_time = time.perf_counter() print("Program finished in {} seconds - using multiprocessing".format(finish_time-start_time)) print("---") # second way, serial computation start_time = time.perf_counter() result = [] for x in range(10,N): result.append(cube(x)) finish_time = time.perf_counter() print("Program finished in {} seconds".format(finish_time-start_time))
이전 예제를 다음 예제로 변환하여 사용할 수 있습니다 joblib
: 🎜
> python code.py Program finished in 1.6385094 seconds - using multiprocessing --- Program finished in 2.7373942999999996 seconds
실제로 그것이 무엇을 하는지 직관적으로 확인하세요. delayed()
함수는 함수 호출의 "지연된" 버전을 생성하는 다른 함수 주위의 래퍼입니다. 이는 호출 시 함수가 즉시 실행되지 않음을 의미합니다. 🎜
然后,我们多次调用delayed
函数,并传递不同的参数集。例如,当我们将整数1
赋予cube
函数的延迟版本时,我们不计算结果,而是分别为函数对象、位置参数和关键字参数生成元组(cube, (1,), {})
。
我们使用Parallel()
创建了引擎实例。当它像一个以元组列表作为参数的函数一样被调用时,它将实际并行执行每个元组指定的作业,并在所有作业完成后收集结果作为列表。在这里,我们创建了n_jobs=3
的Parallel()
实例,因此将有三个进程并行运行。
我们也可以直接编写元组。因此,上面的代码可以重写为:
result = Parallel(n_jobs=3)((cube, (i,), {}) for i in range(1,1000))
使用joblib
的好处是,我们可以通过简单地添加一个附加参数在多线程中运行代码:
result = Parallel(n_jobs=3, prefer="threads")(delayed(cube)(i) for i in range(1,1000))
这隐藏了并行运行函数的所有细节。我们只是使用与普通列表理解没有太大区别的语法。
创建多个进程并进行并行计算不一定比串行计算更有效。对于 CPU 密集度较低的任务,串行计算比并行计算快。因此,了解何时应该使用多进程非常重要——这取决于你正在执行的任务。
为了让你相信这一点,让我们看一个简单的例子:
from multiprocessing import Pool import time import math N = 5000000 def cube(x): return math.sqrt(x) if __name__ == "__main__": # first way, using multiprocessing start_time = time.perf_counter() with Pool() as pool: result = pool.map(cube, range(10,N)) finish_time = time.perf_counter() print("Program finished in {} seconds - using multiprocessing".format(finish_time-start_time)) print("---") # second way, serial computation start_time = time.perf_counter() result = [] for x in range(10,N): result.append(cube(x)) finish_time = time.perf_counter() print("Program finished in {} seconds".format(finish_time-start_time))
此代码段基于前面的示例。我们正在解决同样的问题,即计算N
个数的平方根,但有两种方法。第一个涉及 Python 进程的使用,而第二个不涉及。我们使用time
库中的perf_counter()
方法来测量时间性能。
在我的电脑上,我得到了这个结果:
> python code.py Program finished in 1.6385094 seconds - using multiprocessing --- Program finished in 2.7373942999999996 seconds
如你所见,相差不止一秒。所以在这种情况下,多进程更好。
让我们更改代码中的某些内容,例如N
的值。 让我们把它降低到N=10000
,看看会发生什么。
这就是我现在得到的:
> python code.py Program finished in 0.3756742 seconds - using multiprocessing --- Program finished in 0.005098400000000003 seconds
发生了什么?现在看来,多进程是一个糟糕的选择。为什么?
与解决的任务相比,在进程之间拆分计算所带来的开销太大了。你可以看到在时间性能方面有多大差异。
위 내용은 Python 다중 프로세스를 적용하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!