Python 多进程(1)

Process Class

使用 Python 模块的 Process 类创建子进程:

def start_string(len, output):
    """ Each sub process do"""
    str_list = list()
    # "abcdefghijklmnopqrstuvwxyz"
    str_range = string.ascii_lowercase
    for i in range(len):
        str_list.append(random.choice(str_range))
    gen_str = "-".join(str_list) + "-" + str(os.getpid())
    print(gen_str)
    time.sleep(1)
    # add to shared Queue
    output.put(gen_str)


def use_process_main():
    # Define an output queue
    output = multiprocessing.Queue()

    mp_list = list()
    for _ in range(4):
        p = multiprocessing.Process(target=start_string, args=(5, output))
        mp_list.append(p)

    # Run all process
    for mp in mp_list:
        mp.start()

    # Block main process util subprocess finish
    for mp in mp_list:
        mp.join()

    print("============ main code ===========")

    # Get process results from the output queue
    results = [output.get() for p in mp_list]
    print("Queue Result:",  results)

运行结果:

y-u-b-o-e-26373
b-x-y-i-v-26374
o-a-e-p-d-26375
s-c-t-x-j-26376
============ main code ===========
Queue Result: ['y-u-b-o-e-26373', 'o-a-e-p-d-26375', 'b-x-y-i-v-26374', 's-c-t-x-j-26376']

其中 mp.join() 的作用是阻塞主进程,加上 for 循环,就是等待所有子进程执行完,才开始执行for循环下面的代码,也就是看到的 main code 的 print。

把 for 循环的 join 注释掉结果是什么呢 ?,可以看到下面的结果中,main code 的 pring 信息提前了,并没有等所有子进程结束才开始执行。说明此时主进程和4个子进程是并行执行的。

u-k-o-f-h-26640
t-x-l-m-d-26641
============ main code ===========
n-z-o-b-z-26642
c-v-e-g-j-26643
Queue Result: ['u-k-o-f-h-26640', 't-x-l-m-d-26641', 'n-z-o-b-z-26642', 'c-v-e-g-j-26643']

为什么 mp.join() 没有和 mp.start() 在一个 for 里,而是单独一个 for 循环执行 join 操作。可以试下把 mp.start 和 mp.join 放在一个 for 里是什么结果 ?

# Run all process
for mp in mp_list:
    mp.start()
    mp.join()

结果如下:

x-w-w-m-u-26906
q-d-q-t-r-26908
y-z-u-g-v-26910
d-i-t-v-f-26912
============ main code ===========
Queue Result: ['x-w-w-m-u-26906', 'q-d-q-t-r-26908', 'y-z-u-g-v-26910', 'd-i-t-v-f-26912']

由于 join 的目的是阻塞主进程,所以放到一个 for 循环里就变成了顺序开启子进程,且启动一个进程后,就阻塞在那里,直到当前子进程执行完,然后开启下一个子进程并阻塞。这个效果其实和单进程是一样的,因为4个进程并没有并行执行,而变成了顺序执行。

Queue中结果的顺序

有时候我们想指定多进程执行的结果,比如for循环先开启子进程1,后开启子进程2,我们希望子进程1的结果先放到Queue中。但是实际执行的时候呢,几个子进程是并行执行的,谁先执行完谁就把结果放到Queue中,所以最后我们拿到的 Queue result结果是随机的,比如下面这个结果

y-u-b-o-e-26373
b-x-y-i-v-26374
o-a-e-p-d-26375
s-c-t-x-j-26376
============ main code ===========
Queue Result: ['y-u-b-o-e-26373', 'o-a-e-p-d-26375', 'b-x-y-i-v-26374', 's-c-t-x-j-26376']

可以看到,进程执行的顺序是 26373, 26374, 26375, 26376, 子进程在放到 Queue 前有 sleep(1),所以最终执行 put 动作的时候是随机的,最后取到的结果是 ['y-u-b-o-e-26373', 'o-a-e-p-d-26375', 'b-x-y-i-v-26374', 's-c-t-x-j-26376']。那么如何按照我们要求的顺序返回结果呢?有一种简单的思路就是,在 put result 的时候,加入一个 order index, 如 put 一个 tuple(idx, result),这样在取出 Queue result 的时候,尽管 tuple 的顺序是不可控的,但是我们可以根据 tuple 里的 idx 进行排序,进而得到我们想要的顺序。

Pool Class

使用 multiprocessing.Process() 对象来创建子进程有几个问题:

  1. 我们到底需要创建几个 Process ?
  2. 执行完的子进程就被销毁了,能否接着做其他任务 ?
  3. 如何确保返回顺序 ?
  4. 频繁创建创建,会带来一定的 OS 开销,如何避免 ?

上述几个问题可以通过 multiprocessing.Pool 对象来解决,创建进程池的时候可以指定池中 processor 的个数,如 pool = multiprocessing.Pool(processes=3) 就是创建含义3个子进程的进程池,当进程池创建好后,可以向进程池中提交任务,池子中的三个进程谁空闲了,就会做下一个等待任务,这样可以避免频繁创建进程带来的开销。

Pool 对象主要有 4 个方法:

  • apply
  • apply_async
  • map
  • map_async

其中 pool.applypool.map 和 Python 内建的 apply, map 是一个意思,它们都可以阻塞主进程,知道所有的子进程结束,并且可以顺序得到子进程的执行结果,具体看下面的例子:

  1. pool.apply
def squre(x):
    res = x**2
    print("sub process: ", os.getpid(), x)
    return res
def use_pool_apply_main():
    # Define pool of 3 processors
    pool = multiprocessing.Pool(processes=3)

    results = list()
    for i in range(6):
        res = pool.apply(squre, args=(i,))
        results.append(res)

    print("============ main code ===========")

    print("main result: ", results)

结果是:

sub process:  3829 0
sub process:  3830 1
sub process:  3831 2
sub process:  3829 3
sub process:  3830 4
sub process:  3831 5
============ main code ===========
main result:  [0, 1, 4, 9, 16, 25]
  1. pool.map
def use_pool_map_main():
    # Define pool of 3 processors
    pool = multiprocessing.Pool(processes=3)

    results = pool.map(squre, range(6))

    print("============ main code ===========")

    print("main result: ", results)

结果是:

sub process:  4094 0
sub process:  4095 1
sub process:  4096 2
sub process:  4094 3
sub process:  4095 4
sub process:  4094 5
============ main code ===========
main result:  [0, 1, 4, 9, 16, 25]

map 接收的是一个可迭代的 iterable 对象,返回的也是一个可迭代的结构,包含每个子进程的结果。

  1. pool.apply_async

async 变量表明这是异步的方法,它会一次开启所有子进程,并且不会阻塞主进程。需要注意的是,它返回的是一个 object, multiprocessing.pool.ApplyResult 对象,需要调用 .get 方法显示结果。

def use_pool_apply_async_main():
    # Define pool of 3 processors
    pool = multiprocessing.Pool(processes=3)

    results = list()
    for i in range(6):
        # 返回的是一个对象,multiprocessing.pool.ApplyResul
        res = pool.apply_async(squre, args=(i,))
        results.append(res)

    print("============ main code ===========")

    print("main result: ", [p.get() for p in results])

结果是:

============ main code ===========
sub process:  5418 2
sub process:  5417 1
sub process:  5416 0
sub process:  5418 3
sub process:  5416 4
sub process:  5417 5
main result:  [0, 1, 4, 9, 16, 25]

因为没有阻塞主进程,可以看到 main code 的 print 信息时先输出的,要想实现阻塞主进程,需要调用 pool.join() 方法:

def use_pool_apply_async_main():
    # Define pool of 3 processors
    pool = multiprocessing.Pool(processes=3)

    results = list()
    for i in range(6):
        # 返回的是一个对象,multiprocessing.pool.ApplyResul
        res = pool.apply_async(squre, args=(i,))
        results.append(res)
    # async 变量
    pool.close()
    pool.join()

    print("============ main code ===========")

    print("main result: ", [p.get() for p in results])
  1. pool.map_async

和 map 一样,接收一个可迭代对象作为参数,返回的不再是迭代对象,而是一个封装过的 MapResult 对象,需要调用 .get 来输出全部内容:

def use_pool_apply_map_main():
    # Define pool of 3 processors
    pool = multiprocessing.Pool(processes=3)

    results = pool.map_async(squre, range(6))

    print("============ main code ===========")

    print("main result: ", results.get())

和 apply_async 一样,没有阻塞主进程,如果想实现阻塞,同样需要调用 pool.join() 方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值