python 并发学习(基于进程的并行)

背景

在之前的章节中,我们见识了如何用线程实现并发的应用。本章节将会介绍基于进程的并行。本章的重点将会集中在Python的 multiprocessing 和 mpi4py 这两个模块上。

multiprocessing 是Python标准库中的模块,实现了共享内存机制,也就是说,可以让运行在不同处理器核心的进程能读取共享内存。

mpi4py 库实现了消息传递的编程范例(设计模式)。简单来说,就是进程之间不靠任何共享信息来进行通讯(也叫做shared nothing),所有的交流都通过传递信息代替。

这方面与使用共享内存通讯,通过加锁或类似机制实现互斥的技术行成对比。在信息传递的代码中,进程通过 send() 和 receive 进行交流。

产生一个进程

“产生”(spawn)的意思是, 由父进程创建子进程。 父进程既可以在产生子进程之后继续异步执行, 也可以暂停等待子进程创建完成之后再继续执行。Python 的多进程可以通过下面的步骤创建:
1 创建进程对象
2 调用start 方法, 开启进程活动
3 调用join()方法, 在进程结束之前一直等待


import multiprocessing

def foo(i):
    print ('called function in process: %s' %i)
    return

if __name__ == '__main__':
    Process_jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=foo, args=(i,))
        Process_jobs.append(p)
        p.start()
        p.join()

如果没有 join() ,主进程退出之后子进程会留在idle中,你必须手动杀死它们。

注意
子进程创建的时候需要导入包含目标函数的脚本。通过在 main 代码块中实例化进程对象,我们可以预防无限递归调用。最佳实践是在不同的脚本文件中定义目标函数,然后导入进来使用。所以上面的代码可以修改为:

import multiprocessing
import target_function
if __name__ == '__main__':
    Process_jobs = []
    for i in range(5):
        p = multiprocessing.Process(target=target_function.function,args=(i,))
        Process_jobs.append(p)
        p.start()
        p.join()

为一个进程命名

import multiprocessing
import time

def foo():
    name = multiprocessing.current_process().name
    print("Starting %s \n" % name)
    time.sleep(3)
    print("Exiting %s \n" % name)

if __name__ == '__main__':
    process_with_name = multiprocessing.Process(name='foo_process', target=foo)
    process_with_name.daemon = True  # 注意原代码有这一行,但是译者发现删掉这一行才能得到正确输出
    process_with_default_name = multiprocessing.Process(target=foo)
    process_with_name.start()
    process_with_default_name.start()

在后台运行一个进程

如果需要处理比较巨大的任务,又不需要人为干预,将其作为后台进程执行是个非常常用的编程模型。此进程又可以和其他进程并发执行。通过Python的multiprocessing模块的后台进程选项,我们可以让进程在后台运行。

import multiprocessing
import time

def foo():
    name = multiprocessing.current_process().name
    print("Starting %s " % name)
    time.sleep(3)
    print("Exiting %s " % name)

if __name__ == '__main__':
    background_process = multiprocessing.Process(name='background_process', target=foo)
    background_process.daemon = True
    NO_background_process = multiprocessing.Process(name='NO_background_process', target=foo)
    NO_background_process.daemon = False
    background_process.start()
    NO_background_process.start()

运行结果

start name no_process
exiting no_process

在非后台运行的进程会看到一个输出,后台运行的没有输出,后台运行进程在主进程结束之后会自动结束。

但是如果加join() 方法之后就不能 在后台运行啦也是会打印出来的。线程中 join 所完成的工作就是线程同步, 即主要线程任务结束之后, 进入阻塞状态, 一直等到其他的子线程执行结束之后, 主线程终止。

要想使用后台进行不能添加join(). join()作用是 阻塞当前线程, 意思是 主线程只有在join调用的线程结束才往下处理。 这个线程处理不完没有结束, 主线程也不能结束

使用多进程的常规方法是,先依次调用start启动进程,再依次调用join要求主进程等待子进程的结束

python多进程的理解 multiprocessing Process join run
时间:2014-09-21 16:34:02 阅读:11202 评论:0 收藏:0 [点我收藏+]

标签:class style 代码 src 使用 方法 http si .net

最近看了下多进程。

一种接近底层的实现方法是使用 os.fork()方法,fork出子进程。但是这样做事有局限性的。比如windows的os模块里面没有 fork() 方法。

windows:bubuko.com,布布扣。linux:bubuko.com,布布扣

另外还有一个模块:subprocess。这个没整过,但从vamei的博客里看到说也同样有局限性。

所以直接说主角吧 — multiprocessing模块。 multiprocessing模块会在windows上时模拟出fork的效果,可以实现跨平台,所以大多数都使用multiprocessing。

下面给一段简单的代码,演示一下创建进程:

encoding:utf-8

from multiprocessing import Process
import os, time, random

线程启动后实际执行的代码块

def r1(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())

def r2(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())

if name == “main“:
print “main process run…”
p1 = Process(target=r1, args=(‘process_name1‘, )) #target:指定进程执行的函数,args:该函数的参数,需要使用tuple
p2 = Process(target=r2, args=(‘process_name2‘, ))

    p1.start()    #通过调用start方法启动进程,跟线程差不多。
    p2.start()    #但run方法在哪呢?待会说。。。
    p1.join()     #join方法也很有意思,寻思了一下午,终于理解了。待会演示。
    p2.join()
    print "main process runned all lines..."

执行结果:

bubuko.com,布布扣

上面提到了两个方法:run 和join

run:如果在创建Process对象的时候不指定target,那么就会默认执行Process的run方法:

encoding:utf-8

from multiprocessing import Process
import os, time, random

def r():
print ‘run method‘

if name == “main“:
print “main process run…”
#没有指定Process的targt
p1 = Process()
p2 = Process()
#如果在创建Process时不指定target,那么执行时没有任何效果。因为默认的run方法是判断如果不指定target,那就什么都不做
#所以这里手动改变了run方法
p1.run = r
p2.run = r

    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print "main process runned all lines..."

另:python源码里,Process.run方法:

bubuko.com,布布扣

执行结果:

bubuko.com,布布扣

可见如果在实例化Process时不指定target,就会执行默认的run方法。

还有一个join方法:

最上面演示的代码中,在调用Process的start方法后,调用了两次join方法。这个join方法是干什么的呢?

官方文档的意思是:阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。

比如还是刚才的代码,只是把两个join注释掉了:

encoding:utf-8

from multiprocessing import Process
import os, time, random

def r1(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
def r2(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())

if name == “main“:
print “main process run…”
p1 = Process(target=r1, args=(‘process_name1‘, ))
p2 = Process(target=r2, args=(‘process_name2‘, ))

    p1.start()
    p2.start()
    #p1.join()
    #p2.join()    
    print "main process runned all lines..."

执行结果:

bubuko.com,布布扣

发现主进程不像之前那样,等待两个子进程执行完了,才继续执行。而是启动两个进程后立即向下执行。

为了深刻理解,这次把p2的执行函数里面睡眠时间调大,让他多睡一会,然后保留p1的join,注释掉p2的join,效果更明显:

encoding:utf-8

from multiprocessing import Process
import os, time, random

def r1(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random())
def r2(process_name):
for i in range(5):
print process_name, os.getpid() #打印出当前进程的id
time.sleep(random.random()*2)

if name == “main“:
print “main process run…”
p1 = Process(target=r1, args=(‘process_name1‘, ))
p2 = Process(target=r2, args=(‘process_name2‘, ))

    p1.start()
    p2.start()
    p1.join()
    #p2.join()    
    print "main process runned all lines..."

执行结果:

bubuko.com,布布扣

发现主线程只是等待p1完成了,就会向下执行,而不会等待p2是否完成。

所以使用多进程的常规方法是,先依次调用start启动进程,再依次调用join要求主进程等待子进程的结束。

然而为什么要先依次调用start再调用join,而不是start完了就调用join呢

p1.start()
p1.join()
p2.start()

join是用来阻塞当前线程的,p1.start()之后,p1就提示主线程,需要等待p1结束才向下执行,那主线程就乖乖的等着啦,自然没有执行p2.start()这一句

后台进程不允许创建子进程。否则,当后台进程跟随父进程退出的时候,子进程会变成孤儿进程。另外,它们并不是Unix的守护进程或服务(daemons or services),所以当非后台进程退出,它们会被终结。

杀死一个进程

可以使用 terminate() 方法立即杀死一个进程。另外,我们可以使用 is_alive() 方法来判断一个进程是否还存活。
创建一个目标函数为 foo() 的进程。启动之后,我们通过 terminate(0 方法杀死它。

# 杀死一个进程
import multiprocessing
import time

def foo():
        print('Starting function')
        time.sleep(0.1)
        print('Finished function')

if __name__ == '__main__':
        p = multiprocessing.Process(target=foo)
        print('Process before execution:', p, p.is_alive())
        p.start()
        print('Process running:', p, p.is_alive())
        p.terminate()
        print('Process terminated:', p, p.is_alive())
        p.join()
        print('Process joined:', p, p.is_alive())
        print('Process exit code:', p.exitcode)

我们创建了一个线程,然后用 is_alive() 方法监控它的声明周期。然后通过调用 terminate() 方法结束进程。

最后,我们通过读进程的 ExitCode 状态码(status code)验证进程已经结束, ExitCode 可能的值如下:

== 0: 没有错误正常退出

0: 进程有错误,并以此状态码退出
< 0: 进程被 -1 * 的信号杀死并以此作为 ExitCode 退出
在我们的例子中,输出的 ExitCode 是 -15 。负数表示子进程被数字为15的信号杀死

如何在子类中使用进程

# -*- coding: utf-8 -*-
# 自定义子类进程
import multiprocessing

class MyProcess(multiprocessing.Process):
        def run(self):
                print ('called run method in process: %s' % self.name)
                return

if __name__ == '__main__':
        jobs = []
        for i in range(5):
                p = MyProcess()
                jobs.append(p)
                p.start()
                p.join()

运行结果

called run method in process: MyProcess-1
called run method in process: MyProcess-2
called run method in process: MyProcess-3
called run method in process: MyProcess-4
called run method in process: MyProcess-5

实现一个自定义的进程子类,需要以下三步:

定义 Process 的子类
覆盖 __init__(self [,args]) 方法来添加额外的参数
覆盖 run(self, [.args]) 方法来实现 Process 启动的时候执行的任务

创建 Porcess 子类之后,你可以创建它的实例并通过 start() 方法启动它,启动之后会运行 run() 方法。

在进程之间交换对象

并行通常要在程序中交换数据,Multiprocessing 库 有两个Communication Channel 可以交换对象。:队列和管道
这里写图片描述

 使用队列交换对象

我们可以通过队列数据结构来共享对象。

Queue 返回一个进程共享的队列,是线程安全的,也是进程安全的。任何可序列化的对象(Python通过 pickable 模块序列化对象)都可以通过它进行交换。

这里写图片描述

import multiprocessing
import random
import time

class Producer(multiprocessing.Process):
    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue

    def run(self):
        for i in range(10):
            item = random.randint(0, 256)
            self.queue.put(item)
            print("Process Producer : item %d appended to queue %s" % (item, self.name))
            time.sleep(1)
            print("The size of queue is %s" % self.queue.qsize())

class Consumer(multiprocessing.Process):
    def __init__(self, queue):
        multiprocessing.Process.__init__(self)
        self.queue = queue

    def run(self):
        while True:
            if self.queue.empty():
                print("the queue is empty")
                break
            else:
                time.sleep(2)
                item = self.queue.get()
                print('Process Consumer : item %d popped from by %s \n' % (item, self.name))
                time.sleep(1)

if __name__ == '__main__':
    queue = multiprocessing.Queue()
    process_producer = Producer(queue)
    process_consumer = Consumer(queue)
    process_producer.start()
    process_consumer.start()
    process_producer.join()
    process_consumer.join()

更多相关

  • 队列还有一个 JoinaleQueue 子类,它有以下两个额外的方法:
    task_done(): 此方法意味着之前入队的一个任务已经完成,比如, get() 方法从队列取回item之后调用。所以此方法只能被队列的消费者调用。
  • join(): 此方法将进程阻塞,直到队列中的item全部被取出并执行。

task_done(): 此方法意味着之前入队的一个任务已经完成,比如, get() 方法从队列取回item之后调用。所以此方法只能被队列的消费者调用。
join(): 此方法将进程阻塞,直到队列中的item全部被取出并执行。

使用管道交换对象

Communication Channel是管道。
管道可以做以下事情:
- 返回一对被管道连接的连接对象
- 然后对象就有了 send /recieve 方法在进程之间通信

import multiprocessing

def create_items(pipe):
    output_pipe, _ = pipe
    for item in range(10):
        output_pipe.send(item)
    output_pipe.close()

def multiply_items(pipe_1, pipe_2):
    close, input_pipe = pipe_1
    close.close()
    output_pipe, _ = pipe_2
    try:
        while True:
            item = input_pipe.recv()
            output_pipe.send(item * item)
    except EOFError:
        output_pipe.close()

if __name__== '__main__':
    # 第一个进程管道发出数字
    pipe_1 = multiprocessing.Pipe(True)
    process_pipe_1 = multiprocessing.Process(target=create_items, args=(pipe_1,))
    process_pipe_1.start()
    # 第二个进程管道接收数字并计算
    pipe_2 = multiprocessing.Pipe(True)
    process_pipe_2 = multiprocessing.Process(target=multiply_items, args=(pipe_1, pipe_2,))
    process_pipe_2.start()
    pipe_1[0].close()
    pipe_2[0].close()
    try:
        while True:
            print(pipe_2[1].recv())
    except EOFError:
        print("End")

运行结果

0
1
4
9
16
25
36
49
64
81
End

进程间同步

多个进程可以协同工作来完成一项任务。通常需要共享数据。所以在多进程之间保持数据的一致性就很重要了。需要共享数据协同的进程必须以适当的策略来读写数据。相关的同步原语和线程的库很类似。
进程的同步原语如下:

  • Lock: 这个对象可以有两种装填:锁住的(locked)和没锁住的(unlocked)。一个Lock对象有两个方法, acquire() 和 release() ,来控制共享数据的读写权限。
  • Event: 实现了进程间的简单通讯,一个进程发事件的信号,另一个进程等待事件的信号。 Event 对象有两个方法, set() 和 clear() ,来管理自己内部的变量。
  • Condition: 此对象用来同步部分工作流程,在并行的进程中,有两个基本的方法: wait() 用来等待进程, notify_all() 用来通知所有等待此条件的进程。
  • Semaphore: 用来共享资源,例如,支持固定数量的共享连接。
  • Rlock: 递归锁对象。其用途和方法同 Threading 模块一样。
  • Barrier: 将程序分成几个阶段,适用于有些进程必须在某些特定进程之后执行。处于障碍(Barrier)之后的代码不能同处于障碍之前的代码并行。
import multiprocessing
from multiprocessing import Barrier, Lock, Process
from time import time
from datetime import datetime

def test_with_barrier(synchronizer, serializer):
    name = multiprocessing.current_process().name
    synchronizer.wait()
    now = time()
    with serializer:
        print("process %s ----> %s" % (name, datetime.fromtimestamp(now)))

def test_without_barrier():
    name = multiprocessing.current_process().name
    now = time()
    print("process %s ----> %s" % (name, datetime.fromtimestamp(now)))

if __name__ == '__main__':
    synchronizer = Barrier(2)
    serializer = Lock()
    Process(name='p1 - test_with_barrier', target=test_with_barrier, args=(synchronizer,serializer)).start()
    Process(name='p2 - test_with_barrier', target=test_with_barrier, args=(synchronizer,serializer)).start()
    Process(name='p3 - test_without_barrier', target=test_without_barrier).start()
    Process(name='p4 - test_without_barrier', target=test_without_barrier).start()
process p3 - test_without_barrier ----> 2018-07-06 14:17:20.416147
process p4 - test_without_barrier ----> 2018-07-06 14:17:20.422132
process p2 - test_with_barrier ----> 2018-07-06 14:17:20.432129
process p1 - test_with_barrier ----> 2018-07-06 14:17:20.432129

这里写图片描述

p1 p2 相同

这里写图片描述

Barrier 类设置了一个线程数量障碍, 当等待的线程到达这个数量的时候就会唤醒所有等待线程
init(self, parties, action=None, timeout=None) parties障碍要求的线程数量 action设置了的话会在突破障碍的时候被某一个被唤醒的线程调用 timeout给之后的wait()设置了个默认的等待时间

wait(self, timeout=None) 当前线程进入阻塞状态

abort(self) 强行突破阻碍,所有正在等待的线程和要调用wait()方法的线程收到一个BrokenBarrierError异常

reset(self) 重置当前对象,所有正在等待的线程收到一个BrokenBarrierError异常

如何在进程之间管理状态

python 多进程模块提供了在所有的用户间管理共享信息的管理者(mananger). 一个管理者对象控制着持有python 对象的服务进程, 并允许其他进程操作共享对象。

1 它控制着管理共享对象的服务进程
2 它确保当某个进程修改了共享对象之后, 所有的进程拿到的共享对象都得到更新。

看个进程之间共享对象的例子

1 首先,程序创建了一个管理者的字典,在 n 个 taskWorkers 之间共享,每个worker更新字典的某一个index。
2 所有的worker完成之后,新的列表打印到 stdout :

所有的 进程都是操作这个 manage.

import multiprocessing

def worker(dictionary, key, item):
   dictionary[key] = item
   print("key = %d value = %d" % (key, item))

if __name__ == '__main__':
    mgr = multiprocessing.Manager()
    dictionary = mgr.dict()
    jobs = [multiprocessing.Process(target=worker, args=(dictionary, i, i*2)) for i in range(10)]
    for j in jobs:
        j.start()
    for j in jobs:
        j.join()
    print('Results:', dictionary)
key = 2 value = 4
key = 5 value = 10
key = 6 value = 12
key = 3 value = 6
key = 7 value = 14
key = 1 value = 2
key = 0 value = 0
key = 4 value = 8
key = 8 value = 16
key = 9 value = 18
Results: {2: 4, 5: 10, 6: 12, 3: 6, 7: 14, 1: 2, 0: 0, 4: 8, 8: 16, 9: 18}

都在操作这个 字典, 最后这个字典的值就是所有人操作的结果

使用进程池

  • apply(): 直到得到结果之前一直阻塞。
  • apply_async(): 这是 apply() 方法的一个变体,返回的是一个result对象。这是一个异步的操作,在所有的子类执行之前不会锁住主进程。
  • map(): 这是内置的 map() 函数的并行版本。在得到结果之前一直阻塞,此方法将可迭代的数据的每一个元素作为进程池的一个任务来执行。
  • map_async(): 这是 map() 方法的一个变体,返回一个result对象。如果指定了回调函数,回调函数应该是callable的,并且只接受一个参数。当result准备好时会自动调用回调函数(除非调用失败)。回调函数应该立即完成,否则,持有result的进程将被阻塞

创建了有4个进程的进程池,然后使用 map() 方法进行一个简单的计算。

import multiprocessing

def function_square(data):
    result = data*data
    return result

if __name__ == '__main__':
    inputs = list(range(10))
    pool = multiprocessing.Pool(processes=4)
    pool_outputs = pool.map(function_square, inputs)
    pool.close()
    pool.join()
    print ('Pool    :', pool_outputs)

运行结果

Pool    : [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

mpi4py 模块

进程线程中的join()方法理解

http://www.bubuko.com/infodetail-374280.html

https://blog.csdn.net/pzqingchong/article/details/79683251

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值