python-multiprocessing

发布时间 2023-03-26 17:18:23作者: 贝壳里的星海

python-multiprocessing

在平常python程序中写入的程序大部分都是基于单进程,无法充分利用cpu多核的功能,python提供了multiprocessing模块来使用多核并发运行的操作,极大提高了程序的效率。multiprocessing 是一个支持使用与 threading 模块类似的 API 来产生进程的包。multiprocessing 包同时提供了本地和远程并发操作,通过使用子进程而非线程有效地绕过了全局解释器锁。

['Array', 'AuthenticationError', 'Barrier', 'BoundedSemaphore', 
 'BufferTooShort', 'Condition', 'Event', 'JoinableQueue', 'Lock', 'Manager', 'Pipe', 'Pool',
 'Process', 'ProcessError', 'Queue', 'RLock', 'RawArray', 'RawValue', 'Semaphore', 'SimpleQueue',
 'TimeoutError', 'Value', 'active_children', 'allow_connection_pickling', 'cpu_count', 'current_process',
 'freeze_support', 'get_all_start_methods', 'get_context', 'get_logger', 'get_start_method', 
 'log_to_stderr', 'reducer', 'set_executable', 'set_forkserver_preload', 'set_start_method'] 
并发类型 切换机制 CPU数量 适用场景 代表Python库
多线程(抢占式多任务处理) 操作系统决定何时切换任务 1个 I/O密集型 threading, cocurrent.futures
异步(协作式多任务处理) 任务本身决定何时切换 1个 I/O密集型 asyncio, netdev, aiohttp, aioping, gevent, tornado, twisted
多进程 (并行) 所有任务同时运行 多个 CPU密集型 multiprocessing

基本概念

  • 运行中的程序就是一个进程
  • 需要占用资源,受操作系统调度
  • 每个进程都有对应的pid,是进程的唯一标识,在其生命周期内不变
  • 进程是计算机中最小的资源分配单位(分配一些内存)
  • 进程之间数据隔离
  • 可以利用多核

进程三状态:就绪/Ready、运行/Running、阻塞/Blocked

Process

Python中的多进程是通过multiprocessing包来实现的,和多线程的threading.Thread差不多,它可以利用multiprocessing.Process对象来创建一个进程对象。

from multiprocessing import Process
Process([group [, target [, name [, args [, kwargs]]]]])
  • target:传递一个函数的引用,可以认为这个子进程就是执行这个函数的代码,这里传的是函数的引用,后面不能有小括号

  • args: 给target指定的函数传递的参数,以元组的方式传递,这里必须是一个元组,如果不是元组会报TypeError,只有一个参数时要注意别出错

  • kwargs:给target指定的函数传递关键字参数,以字典的方式传递,这里必须是一个字典

  • name:给进程设定一个名字,可以不设定

属性和方法

属性和方法 描述
p.start() 启动进程,并调用该子进程中的p.run()
p.run() 进程启动时运行的方法,正是它去调用target指定的函数, 可以在自定义类中实现该方法
p.terminate() 强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive() 如果p仍然运行,返回True
p.join([timeout]) 主线程等待p终止,:是主线程处于等的状态,而p是处于运行的状态)
p.daemon 默认值为False,如果设为True,代表p为后台运行的守护进程
p.name 进程的名称
p.pid 进程的pid
p = Process(target=func, args=(,)) / 或者通过面向对象获取的子进程
p.start()		    # 启动子进程实例(创建子进程)
p.join([timeout])   # 是否等待子进程执行结束
p.ident()			# 获取子进程的pid(与p.pid相同)
p.terminate()		# 强制结束一个子进程
p.is_alive()		# 查看一个子进程是否还活着

属性
p.pid()	    	 	# 获取子进程的pid
p.name()    		# 获取子进程的名字
import time
from multiprocessing import Process

class MyProcess(Process):
    def __init__(self, name):
        super().__init__()
        self.name = name
    
    def run(self):
        print('进程%s开始运行' % self.name)
        time.sleep(2)
        print('进程%s结束运行' % self.name)

if __name__ == '__main__':
    p = MyProcess('P1')
    p.start()
    p.join()
import time,os
from multiprocessing import Process

def run():
    print("子进程开启")
    time.sleep(2)
    print("子进程结束")


if __name__ == "__main__":
    print("父进程启动")
    p = Process(target=run)
    p.start()
    print("父进程结束")

# 输出结果
父进程启动
父进程结束
子进程开启
子进程结束

父进程的结束不能影响子进程。但这样有会出现一个问题,我们有时候会需要父进程等待子进程结束再执行父进程后面的代码

import time,os
from multiprocessing import Process

def run():
    print("子进程开启")
    time.sleep(2)
    print("子进程结束")

if __name__ == "__main__":
    print("父进程启动")
    p = Process(target=run)
    p.start()
    p.join()  # 等待子进程结束后,再往下执行
    print("父进程结束")

# 输出结果
父进程启动
子进程开启
子进程结束
父进程结束

Pool

multiprocessing模块提供了一个进程池Pool类,负责创建进程池对象,并提供了一些方法来讲运算任务offload到不同的子进程中执行,并很方便的获取返回值。

Pool类表示工作进程的进程池。有不同方法让任务转移到工作进程

apply(func[, args[, kwds]])方法是阻塞,意味着当前的进程没有执行完的话,后续的进程需要等待该进程执行结束才能执行,实际上该方法是串行。

apply_async(func[, args[, kwds[, callback[, error_callback]]]])方法是异步非阻塞的,意味着不用等待当前进程执行完成,即可根据系统的调度切换进程,该方法是并行

map(func, iterable[, chunksize])方法将iterable对象分成一些块,作为单独的任务提交给进程池。该方法是阻塞的

map_async(func, iterable[, chunksize[, callback[, error_callback]]])方法是map的变种,是非阻塞的

from multiprocessing import Pool
def main(name, num):
    print(f'{num} {name}: Hello World')

if __name__ == '__main__':
    # 创建进程池
    p = Pool()
    for i in range(5):
        p.apply(func=main, args=('LovefishO', i, ))
    p.close()     # 关闭进程池
    p.join()   # 阻塞进程, 等待子进程执行结束
    print('主进程结束')
from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    with Pool(processes=4) as pool: # 开启4个工作进程
        result = pool.apply(f,[10]) 	# 异步方式计算f(10)
        print(result)
        result=pool.map_async(f, range(10))
        print(result.get())   # 打印 [0,1,4,...,81]

守护进程

正常情况下,当子进程和主进程都结束时,程序才会结束。但是当我们需要在主进程结束时,由该主进程创建的子进程也必须跟着结束时,就需要使用守护进程。当一个子进程为守护进程时,在主进程结束时,该子进程也会跟着结束

from multiprocessing import Process
import time
def main(name):
    print(f'{name}: Hello World')
    time.sleep(2)
    print("线程结束")
    
if __name__ == '__main__':
    # 创建守护进程, 设置daemon = True
    p = Process(target=main, daemon=True, args=('LovefishO',))

    p.start()   # 开始进程  
    # p.join()    # 阻塞进程
# 主进程退出,子进程也随之退出

交换进程的对象

Queues

Queue类与queue.Queue非常相似.Queues是线程和进程安全的。

from multiprocessing import Process, Queue

def f(q):
    q.put([42, None, 'hello'])

if __name__ == '__main__':
    q = Queue()
    p = Process(target=f, args=(q,))
    p.start()
    print(q.get())    # prints "[42, None, 'hello']"
    p.join()  # 使用join方法等待进程结束,防止数据没有读取完成就退出了主进程。

Pipe

表示一个管道,用于在进程间通信。它是一种双向通信方式,允许在两个不同的进程中通过管道进行数据通信。

Pipe可以是单向(half-duplex),也可以是双向(duplex)。我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向),即在一个进程中只能写数据,在另一个进程中只能读取数据。Queue允许多个进程通信

connection1, connection2 = Pipe():创建一个管道。此函数返回两个 Connection 对象,它们分别代表管道的两端,用于读写数据。

send(obj):在当前进程中写入数据。

recv():在当前进程中读取数据。

from multiprocessing import Process, Pipe

def f(conn):
    conn.send([42, None, 'hello'])
    conn.close()

if __name__ == '__main__':
    parent_conn, child_conn = Pipe()
    p = Process(target=f, args=(child_conn,))
    p.start()
    print(parent_conn.recv())   # prints "[42, None, 'hello']"
    p.join()

在进程间分享状态

共享内存

import multiprocessing

# Value/Array
def func1(a, arr):
    a.value = 3.14
    for i in range(len(arr)):
        arr[i] = 0
    a.value = 0

if __name__ == '__main__':
    num = multiprocessing.Value('d', 1.0)  # num=0
    arr = multiprocessing.Array('i', range(10))  # arr=range(10)
    p = multiprocessing.Process(target=func1, args=(num, arr))
    p.start()
    p.join()
    print (num.value)
    print (arr[:])

0.0
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

在主进程的内存空间中创建共享的内存,也就是Value和Array两个对象。对象Value被设置成为双精度数(d), 并初始化为1.0。而Array则类似于C中的数组,有固定的类型(i, 也就是整数)。在Process进程中,我们修改了Value和Array对象。回到主程序,打印出结果,主程序也看到了两个对象的改变

服务进程

Manager是通过共享进程的方式共享数据。
Manager管理的共享数据类型有:Value、Array、dict、list、Lock、Semaphore等等,同时Manager还可以共享类的实例对象。

from multiprocessing import Process,Manager
def func1(shareList,shareValue,shareDict,lock):
    with lock:
        shareValue.value+=1
        shareDict[1]='1'
        shareDict[2]='2'
        for i in range(len(shareList)):
            shareList[i]+=1

if __name__ == '__main__':
    manager=Manager()
    list1=manager.list([1,2,3,4,5])
    dict1=manager.dict()
    array1=manager.Array('i',range(10))
    value1=manager.Value('i',1)
    lock=manager.Lock()
    # lock.acquire()
    # lock.release()
    print (list1)
    print (dict1)
    print (array1)
    print (value1)
    print("进程操作>>>")
    proc=[Process(target=func1,args=(list1,value1,dict1,lock)) for i in range(20)]
    for p in proc:
        p.start()
    for p in proc:
        p.join()
    print (list1)
    print (dict1)
    print (array1)
    print (value1)

[1, 2, 3, 4, 5]
{}
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Value('i', 1)
进程操作>>>
[21, 22, 23, 24, 25]
{1: '1', 2: '2'}
array('i', [0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
Value('i', 21)

参考文献

https://docs.python.org/zh-cn/3/library/multiprocessing.html#process-and-exceptions

https://blog.csdn.net/ctwy291314/article/details/89358144