📜  Python的并发-多处理

📅  最后修改于: 2020-11-08 08:49:52             🧑  作者: Mango


在本章中,我们将更多地关注多处理与多线程之间的比较。

多处理

它是在单个计算机系统中使用两个或多个CPU单元。通过利用计算机系统中可用的CPU内核数量,这是从硬件中获得最大潜能的最佳方法。

多线程

CPU通过同时执行多个线程来管理操作系统使用的能力。多线程的主要思想是通过将一个进程划分为多个线程来实现并行性。

下表显示了它们之间的一些重要区别-

Multiprocessing Multiprogramming
Multiprocessing refers to processing of multiple processes at same time by multiple CPUs. Multiprogramming keeps several programs in main memory at the same time and execute them concurrently utilizing single CPU.
It utilizes multiple CPUs. It utilizes single CPU.
It permits parallel processing. Context switching takes place.
Less time taken to process the jobs. More Time taken to process the jobs.
It facilitates much efficient utilization of devices of the computer system. Less efficient than multiprocessing.
Usually more expensive. Such systems are less expensive.

消除全局解释器锁定(GIL)的影响

在处理并发应用程序时, Python存在一个限制,称为GIL(全局解释器锁定) 。 GIL从未允许我们利用CPU的多个内核,因此我们可以说Python中没有真正的线程。 GIL是互斥锁–互斥锁,可以使线程安全。换句话说,我们可以说GIL阻止了多个线程并行执行Python代码。一次只能由一个线程持有该锁,如果我们要执行一个线程,则它必须首先获取该锁。

通过使用多处理,我们可以有效地绕过由GIL引起的限制-

  • 通过使用多重处理,我们利用了多个进程的功能,因此我们利用了GIL的多个实例。

  • 因此,在任何时候都没有限制在我们的程序中执行一个线程的字节码。

在Python启动进程

以下三种方法可用于在多处理模块中的Python启动进程-

  • 叉子
  • 产生
  • 前叉服务器

用Fork创建流程

Fork命令是UNIX中的标准命令。它用于创建称为子流程的新流程。该子进程与称为父进程的进程同时运行。这些子进程也与其父进程相同,并且继承了所有可用于父进程的资源。在使用Fork创建流程时使用以下系统调用-

  • fork() -这是一个通常在内核中实现的系统调用。它用于创建进程的副本。p>

  • getpid() -此系统调用返回调用进程的进程ID(PID)。

以下Python脚本示例将帮助您了解如何创建新的子进程以及获取子进程和父进程的PID的方法-

import os

def child():
   n = os.fork()
   
   if n > 0:
      print("PID of Parent process is : ", os.getpid())

   else:
      print("PID of Child process is : ", os.getpid())
child()

输出

PID of Parent process is : 25989
PID of Child process is : 25990

使用Spawn创建流程

Spawn意味着开始新的事物。因此,产生一个进程意味着由父进程创建一个新进程。父进程异步继续执行,或者等待直到子进程结束执行。请遵循以下步骤来生成进程-

  • 导入多处理模块。

  • 创建对象进程。

  • 通过调用start()方法来启动流程活动。

  • 等到进程完成工作并通过调用join()方法退出。

以下Python脚本示例有助于生成三个进程

import multiprocessing

def spawn_process(i):
   print ('This is process: %s' %i)
   return

if __name__ == '__main__':
   Process_jobs = []
   for i in range(3):
   p = multiprocessing.Process(target = spawn_process, args = (i,))
      Process_jobs.append(p)
   p.start()
   p.join()

输出

This is process: 0
This is process: 1
This is process: 2

使用Forkserver创建流程

Forkserver机制仅在那些支持通过Unix Pipes传递文件描述符的UNIX平台上可用。考虑以下几点以了解Forkserver机制的工作原理-

  • 使用Forkserver机制实例化服务器以启动新进程。

  • 然后服务器接收命令并处理所有创建新进程的请求。

  • 为了创建一个新进程,我们的Python程序将向Forkserver发送一个请求,它将为我们创建一个进程。

  • 最后,我们可以在程序中使用这个新创建的过程。

Python的守护进程

Python多处理模块允许我们通过其守护程序选项进行守护进程。守护进程或在后台运行的进程遵循与守护线程类似的概念。要在后台执行该过程,我们需要将守护程序标志设置为true。只要主进程正在执行,守护进程便会继续运行,并且在完成执行过程或终止主程序后将终止进程。

在这里,我们使用与守护程序线程中相同的示例。唯一的区别是模块从多线程更改为处理,并将守护程序标志设置为true。但是,输出将发生如下所示的变化-

import multiprocessing
import time

def nondaemonProcess():
   print("starting my Process")
   time.sleep(8)
   print("ending my Process")
def daemonProcess():
   while True:
   print("Hello")
   time.sleep(2)
if __name__ == '__main__':
   nondaemonProcess = multiprocessing.Process(target = nondaemonProcess)
   daemonProcess = multiprocessing.Process(target = daemonProcess)
   daemonProcess.daemon = True
   nondaemonProcess.daemon = False
   daemonProcess.start()
   nondaemonProcess.start()

输出

starting my Process
ending my Process

与守护程序线程生成的输出相比,输出是不同的,因为没有守护程序模式的进程都有输出。因此,守护进程在主程序结束之后自动结束,以避免持续运行的进程。

在Python终止进程

我们可以使用Terminate()方法立即终止或终止进程。在完成执行之前,我们将使用此方法终止已在函数的帮助下创建的子进程。

import multiprocessing
import time
def Child_process():
   print ('Starting function')
   time.sleep(5)
   print ('Finished function')
P = multiprocessing.Process(target = Child_process)
P.start()
print("My Process has terminated, terminating main thread")
print("Terminating Child Process")
P.terminate()
print("Child Process successfully terminated")

输出

My Process has terminated, terminating main thread
Terminating Child Process
Child Process successfully terminated

输出显示,该程序在执行子进程之前终止,该子进程已通过Child_process()函数。这意味着子进程已成功终止。

识别Python的当前过程

操作系统中的每个进程都具有称为PID的进程标识。在Python,我们可以借助以下命令找出当前进程的PID-

import multiprocessing
print(multiprocessing.current_process().pid)

以下Python脚本示例有助于找出主进程的PID以及子进程的PID-

import multiprocessing
import time
def Child_process():
   print("PID of Child Process is: {}".format(multiprocessing.current_process().pid))
print("PID of Main process is: {}".format(multiprocessing.current_process().pid))
P = multiprocessing.Process(target=Child_process)
P.start()
P.join()

输出

PID of Main process is: 9401
PID of Child Process is: 9402

在子类中使用过程

我们可以通过子类化threading.Thread类来创建线程。此外,我们还可以通过对multiprocessing.Process类进行子类化来创建流程。为了在子类中使用流程,我们需要考虑以下几点-

  • 我们需要定义Process类的新子类。

  • 我们需要重写_init_(self [,args])类。

  • 我们需要重写run(self [,args])方法的来实现什么Process

  • 我们需要通过调用start()方法来启动该过程。

import multiprocessing
class MyProcess(multiprocessing.Process):
   def run(self):
   print ('called run method in process: %s' %self.name)
   return
if __name__ == '__main__':
   jobs = []
   for i in range(5):
   P = MyProcess()
   jobs.append(P)
   P.start()
   P.join()

输出

called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5

Python多处理模块–池类

如果我们在Python应用程序中讨论简单的并行处理任务,那么多处理模块将为我们提供Pool类。类的以下方法可用于在主程序中加速子进程的数量

apply()方法

此方法类似于.ThreadPoolExecutor的.submit()方法它会阻塞直到结果准备就绪。

apply_async()方法

当我们需要并行执行任务时,我们需要使用apply_async()方法将任务提交到池中。这是一个异步操作,直到所有子进程都执行后,它才会锁定主线程。

map()方法

就像apply()方法一样,它也会阻塞,直到结果准备好为止。它等效于内置的map()函数,该函数将可迭代的数据拆分为多个块,并作为单独的任务提交给进程池。

map_async()方法

它是map()方法的一种变体,因为apply_async()apply()方法的一部分。它返回一个结果对象。结果准备就绪后,将对其应用可调用对象。可调用对象必须立即完成;否则,处理结果的线程将被阻塞。

以下示例将帮助您实现用于执行并行执行的进程池。通过multiprocessing.Pool方法应用square()函数,可以对数字的平方进行简单的计算。然后使用pool.map()提交5,因为输入是一个从0到4的整数列表。结果将存储在p_outputs中并打印出来。

def square(n):
   result = n*n
   return result
if __name__ == '__main__':
   inputs = list(range(5))
   p = multiprocessing.Pool(processes = 4)
   p_outputs = pool.map(function_square, inputs)
   p.close()
   p.join()
   print ('Pool :', p_outputs)

输出

Pool : [0, 1, 4, 9, 16]