python-multiprocessing
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