python multiprocessing 进程笔记

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()

运行结果为:

apply1
apply2
apply3
apply4
apply5
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结果,是按提交的顺序获取的。

运行结果为:

map0
map1
map2
map3
map4
map5
map6
map7
map8
map9
这是主进程
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 提交,来获取报错信息,没问题后再改用其他的提交方式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值