1、创建进程
import time, os
import multiprocessing as mp
def digui(n): # 先建一个给进程调用的函数(递归函数)
print(__name__, mp.current_process().name ,'开始时间:',time.ctime())
print(f'父进程的ID为:{os.getppid()},子进程的ID为:{os.getpid()}。')
a = time.time()
def f(n): # 这是一个递归公式.
if n ==1:
return 1
elif n == 2:
return 1
elif n > 2:
result = f(n-1)+f(n-2)
return result
print('递归的结果为:',f(n))
b = time.time()
print('结束,用时为:', b - a,'\n')
if __name__ == '__main__': # 这一步一定要加。
'''这是 Windows 上多进程的实现问题。在 Windows 上,子进程会自动 import 启动它的这个文件,
而在 import 的时候是会执行这些语句的。如果你这么写的话就会无限递归创建子进程报错。
所以必须把创建子进程的部分用那个 if 判断保护起来,import 的时候 __name__ 不是 __main__ ,就不会递归运行了。
'''
# mp.set_start_method('spawn') # 选择进程的启动方式。有 'spawn':这是windows默认的,'fork','forkserver'。
print(f'{__name__}>>>:父进程的ID为:{os.getppid()},子进程的ID为:{os.getpid()}。')
print('系统CUP的核心数量为:{}'.fotmat(mp.cpu_count())) # mp.cpu_count() 获取系统的cpu核心个数。
c = time.time()
p1 = mp.Process(target=digui, args=(35,), name='进程1')
p2 = mp.Process(target=digui, args=(36,))
p1.daemon = True # 设置p1为守护进程,为True时,如果p1没加join(),那么当主进程结束时,p1也会随之中断运行。
# 如果加了 p1.join(),肯定都是等子进程结束了,再接着运行后面的代码的,所以daemon这个参数意义就不大了。
# 如果daemon =False (默认的),主进程结束后,子进程继续运行。
p1.start()
p2.start()
p1.join() # 加了join 后面的代码会阻塞,等待p1进程的结束。
p2.join() # 如果不加join 后面的代码会继续运行下去,不会等待子进程。
# p1.terminate() # terminate() 用来结束进程的。一般用不到这个,因为一般在进程引用的函数结束后,进程自动结束。
# p2.terminate() # 除非在一些不断循环的情况下,用来结束进程。
d = time.time()
print('进程总用时:', d - c)
2、使用进程中的队列,进行进程之间的通信
import multiprocessing as mp
import time, random
def washer(dishes, output):
for dish in dishes:
time.sleep(random.random() / 2)
print('Washing', dish, ' dish')
output.put(dish) # 把元素放入队列中。
def dryer(input):
while True:
dish = input.get() # get()取出元素
time.sleep(random.random())
print('Drying', dish, 'dish')
input.task_done()
''' task_done():指出之前进入队列的任务已经完成。
对于每次调用 get() 获取的任务,执行完成后调用 task_done() 告诉队列该任务已经处理完成。
如果 join() 方法正在阻塞之中,该方法会在所有对象都被处理完的时候返回
(即对之前使用 put() 放进队列中的所有对象都已经返回了对应的 task_done() ) 。
如果被调用的次数多于放入队列中的项目数量,将引发 ValueError 异常 。
'''
if __name__ == "__main__":
dish_queue = mp.JoinableQueue() # 先进先出队列。
dryer_proc = mp.Process(target=dryer, args=(dish_queue,))
dryer_proc.daemon = True
dryer_proc.start()
dishes = [f"{i:02d}" for i in range(15)]
washer(dishes, dish_queue)
dish_queue.join() # 如果不加这句,不等队列中的数据处理完,程序就会结束了。
''' join()阻塞至队列中所有的元素都被接收和处理完毕。
当条目添加到队列的时候,未完成任务的计数就会增加。
每当进程调用 task_done() 表示这个条目已经被回收,该条目所有工作已经完成,未完成计数就会减少。
当未完成计数降到零的时候, join() 阻塞被解除。'''
3、进程池
3.1 普通创建进程池,及提交子进程的方法
from multiprocessing import Pool
import os, time
def long_time_task(name):
print()
print('Run task %s (%s)...' % (name, os.getpid()))
start = time.time()
time.sleep(3)
end = time.time()
print('Task %s runs %0.2f seconds.' % (name, (end - start)))
if __name__=='__main__':
print('Parent process %s.' % os.getpid())
p = Pool(4) # 初始进程池中进程的数量为4个,如果创建的子进程数量超过4个,那其他的任务就会等有空闲的进程来分配给它。
for i in range(5):
p.apply_async(long_time_task, args=(i,)) # apply_async 创建异步进程。
#p.apply (long_time_task, args=(i,)) # 创建同步进程,即进程会按照顺序一个个执行。如果用同步,无需调用close()和join()。
print('Waiting for all subprocesses done...')
p.close() # 阻止向进程池提交任何其他任务。一旦所有任务完成,工作进程将退出。如果不加close(),
# 进程池会等待新的任务,那下面的join()就无法正常结束了。
p.join() # join() 等待所有的子进程结束。
print('All subprocesses done.')
进程池参数说明:
p.apply_async( func,args = () , callback = None) : 异步进程,也就是池中的进程一次性都去执行任务.
func : 进程池中的进程执行的任务函数
args : 可迭代对象性的参数,是传给任务函数的参数
callback : 回调函数,就是每当进程池中有进程处理完任务了,返回的结果可以交给回调函数,由回调函数进行进一步处理,回调函数只异步才有,同步没有.回调函数是父进程调用。
callback调用函数时会给它传入一个参数,这个参数就是每个子进程运行结束的返回值,所以定义一个回调函数时,需要一个位置参数:例
def call_back(res):
print(f' {mp.current_process().name} 的返回值为 {res}')
异步处理任务时 : 必须要加上close和join。 进程池的所有进程都是守护进程(主进程代码执行结束,守护进程就结束).
3.2 用 with 方式创建进程池
用 with 上下文方式创建进程池的好处就是,不用手动的去关闭进程池。运行完 with 中的代码后,它会自动的关闭进程池,并释放资源。
3.2.1 用 apply 提交进程。
apply() 会提交一个子进程,并阻塞主进程,等这个子进程运行结束,然后再提交下一个子进程,就是按代码顺序,一个个运行下去的……
如果 子进程有报错,不用我们去获取子进程的返回值,它就会直接把报错信息返回到主进程中,并终止程序。
from multiprocessing import Pool
import time
def f_apply(x):
print(f'apply:{x}')
# x/0
time.sleep(1)
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool:
res1 = pool.apply(f_apply, args=(1,))
res2 = pool.apply(f_apply, args=(2,))
res3 = pool.apply(f_apply, args=(3,))
res4 = pool.apply(f_apply, args=(4,))
res5 = pool.apply(f_apply, args=(5,))
print(res1, res2, res3, res4, res5)
# 无需调用pool.close(),和pool.join()
运行结果为:
apply:1
apply:2
apply:3
apply:4
apply:5
1 4 9 16 25
3.2.2 用 apply_async 提交进程。
apply_async() , 它会提交一个子进程,子进程会进入“后台”运行,不会阻塞主进程。
然后马上继续提交下一个子进程……
如果 子进程内部有报错,是不会直接把报错信息返回到主进程中的。就是说如果有一个子进程运行到错误代码了,这个子进程只会自己终止运行,而我们获取不到报错信息,它也不会影响到其他子进程和主进程。除非需要获取子进程的返回结果时,主进程才会收到报错信息,并终止主程序。
from multiprocessing import Pool
import time
def f_apply_async(x):
print(f'apply_async:{x}')
#x/0
time.sleep(2)
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool:
res1 = pool.apply_async(f_apply_async, args=(1,))
res2 = pool.apply_async(f_apply_async, args=(2,))
res3 = pool.apply_async(f_apply_async, args=(3,))
res4 = pool.apply_async(f_apply_async, args=(4,))
res5 = pool.apply_async(f_apply_async, args=(5,))
print('这是主进程')
# 用get()获取子进程的返回值时,会阻塞主进程的的。
# print(res1.get(), res2.get(), res3.get(), res4.get(), res5.get()) # 用get()获取子进程的返回值。
pool.close()
pool.join()
3.2.3 用 map() 提交进程。
map() 提交子进程,会马上执行,执行顺序按提交的先后顺序执行的。且会阻塞主进程,直到map中所有的子进程全部运行结束。
对于处理报错,它和 apply()提交的差不多,如果 子进程有报错,不用我们去获取子进程的返回值,它就会直接把报错信息返回到主进程中,并终止主程序,其他的子进程会正常的执行结束。
from multiprocessing import Pool, TimeoutError
import time, random
def f_map(x):
print(f'map:{x}')
# x/0
time.sleep(random.random())
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool:
res = pool.map(f_map, range(10)) # 会等到map中的进程全部结束,
print('这是主进程') # 再运行这行print()代码。
print(f'map运行结果为:{res}') # 获取的res结果,是按提交的顺序获取的。
运行结果为:
map:0
map:1
map:2
map:3
map:4
map:5
map:6
map:7
map:8
map:9
这是主进程
map运行结果为:[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
3.2.4 用 imap() 提交进程。
imap()和map()类似,但它是延迟启动子进程的,就是imap()会把子进程“丢到后台”启动,所以它不会阻塞主线程。
对于报错信息处理,因为它的子进程是“放入后台”的,所以和 apply_async() 效果一样,只有在获取它的返回值时,才会触发它的报错,但不影响其他的子进程。
from multiprocessing import Pool
import time, random
def f_imap(x):
print(f'imap:{x}')
# x/0
time.sleep(random.random())
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool:
res = pool.imap(f_imap, range(10)) # 在后台启动,
print('这是主进程') # 所以这行的print()代码可能会先于子进程运行。
""" 保持主进程持续1秒。因为如果没有sleep(1),
那么当上面执行完print('这是主进程')后,
进程池就会被wih 语句关闭,导致子进程无法执行完毕。"""
# time.sleep(1)
""" 也可以用获取子进程的执行结果(获取的结果与子进程的提交顺序一致),
来阻塞主进程,以等待子进程。"""
print(list(res))
# 也可以用 close() 和 join() 来等待进程池中所有的子进程运行完毕。
# pool.close()
# pool.join()
运行结果为:
这是主进程
imap:0
imap:1
imap:2
imap:3
imap:4
imap:5
imap:6
imap:7
imap:8
imap:9
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
3.2.5 用 imap_unordered 提交进程。
imap_unordered 和 imap 类似,也是延迟启动子进程的,所以它不会阻塞主线程。
与imap() 的区别就是,imap_unordered 获取的返回值可能是与子进程的提交顺序不一致的,
它是按子进程的运行结束时间先后顺序获取的,越早结束的子进程,其结果排在前面。
对于报错信息处理,因为它的子进程是“放入后台”的,所以和 apply_async() 效果一样,只有在获取它的返回值时,才会触发它的报错,但不影响其他的子进程。
from multiprocessing import Pool
import time, random
def f_imap_unordered(x):
print(f'imap_unordered:{x}')
time.sleep(random.random())
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool:
res = pool.imap_unordered(f_imap_unordered, range(10)) # 在后台启动,
print('这是主进程') # 所以这行的print()代码可能会先于子进程运行。
""" 获取子进程的执行结果(获取的结果顺序与子进程的运行时间长短一致),
来阻塞主进程,以等待子进程。"""
print(list(res))
运行结果为:
这是主进程
imap_unordered:0
imap_unordered:1
imap_unordered:2
imap_unordered:3
imap_unordered:4
imap_unordered:5
imap_unordered:6
imap_unordered:7
imap_unordered:8
imap_unordered:9
[1, 9, 4, 0, 16, 49, 25, 36, 81, 64]
Process finished with exit code 0
3.2.6 用 map_async 提交进程。
map_async 和 imap类似,以异步的方式启动子进程的,所以它不会阻塞主线程。
对于报错信息处理,因为它的子进程是“放入后台”的,所以和 apply_async() 效果一样,只有在获取它的返回值时,才会触发它的报错,但不影响其他的子进程。
from multiprocessing import Pool
import time, random
def f_map_async(x):
print(f'map_async:{x}')
# if x == 2:
# x/0 # async 方式,子进程中报错,报错结果不会返回。
time.sleep(random.random())
return x*x
if __name__ == '__main__':
with Pool(processes=4) as pool:
res = pool.map_async(f_map_async, range(10)) # 在后台启动,
print('这是主进程') # 所以这行的print()代码可能会先于子进程运行。
""" 保持主进程持续2秒。因为如果没有sleep(2),
那么当上面执行完print('这是主进程')后,
进程池就会被wih 语句关闭,导致子进程无法执行完毕。"""
# time.sleep(2) # 异步中的报错不会返回。
""" 用get()方法获取子进程的执行结果,
来阻塞主进程,以等待子进程。
(获取的结果顺序与子进程提交顺序一致)"""
print(res.get()) # 用get()获取结果,那么异步中的报错就会返回。
执行结果:
这是主进程
map_async:0
map_async:1
map_async:2
map_async:3
map_async:4
map_async:5
map_async:6
map_async:7
map_async:8
map_async:9
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Process finished with exit code 0
小提示:因为子进程的报错信息,只有在 apply 和 map 中提交,直接运行的情况下会触发。在其他模式下提交,不去获取返回值值,是不会报错的。为了便于编程时调试,可以先用 apply 和 map 提交,来获取报错信息,没问题后再改用其他的提交方式。