时间:2021-05-22
上一篇文章介绍了线程的使用。然而 Python 中由于 Global Interpreter Lock (全局解释锁 GIL )的存在,每个线程在在执行时需要获取到这个 GIL ,在同一时刻中只有一个线程得到解释锁的执行, Python 中的线程并没有真正意义上的并发执行,多线程的执行效率也不一定比单线程的效率更高。 如果要充分利用现代多核 CPU 的并发能力,就要使用 multipleprocessing 模块了。
0x01 multipleprocessing
与使用线程的 threading 模块类似, multipleprocessing 模块提供许多高级 API 。最常见的是 Pool 对象了,使用它的接口能很方便地写出并发执行的代码。
from multiprocessing import Pooldef f(x): return x * xif __name__ == '__main__': with Pool(5) as p: # map方法的作用是将f()方法并发地映射到列表中的每个元素 print(p.map(f, [1, 2, 3]))# 执行结果# [1, 4, 9]关于 Pool 下文中还会提到,这里我们先来看 Process 。
Process
要创建一个进程可以使用 Process 类,使用 start() 方法启动进程。
from multiprocessing import Processimport osdef echo(text): # 父进程ID print("Process Parent ID : ", os.getppid()) # 进程ID print("Process PID : ", os.getpid()) print('echo : ', text)if __name__ == '__main__': p = Process(target=echo, args=('hello process',)) p.start() p.join()# 执行结果# Process Parent ID : 27382# Process PID : 27383# echo : hello process进程池
正如开篇提到的 multiprocessing 模块提供了 Pool 类可以很方便地实现一些简单多进程场景。 它主要有以下接口
map_async() 和 apply_async() 执行后会返回一个 class multiprocessing.pool.AsyncResult 对象,通过它的 get() 可以获取到执行结果, ready() 可以判断 AsyncResult 的结果是否准备好。
进程间数据的传输
multiprocessing 模块提供了两种方式用于进程间的数据共享:队列( Queue )和管道( Pipe )
Queue 是线程安全,也是进程安全的。使用 Queue 可以实现进程间的数据共享,例如下面的 demo 中子进程 put 一个对象,在主进程中就能 get 到这个对象。 任何可以序列化的对象都可以通过 Queue 来传输。
from multiprocessing import Process, Queuedef f(q): q.put([42, None, 'hello'])if __name__ == '__main__': # 使用Queue进行数据通信 q = Queue() p = Process(target=f, args=(q,)) p.start() # 主进程取得子进程中的数据 print(q.get()) # prints "[42, None, 'hello']" p.join()# 执行结果# [42, None, 'hello']Pipe() 返回一对通过管道连接的 Connection 对象。这两个对象可以理解为管道的两端,它们通过 send() 和 recv() 发送和接收数据。
from multiprocessing import Process, Pipedef write(conn): # 子进程中发送一个对象 conn.send([42, None, 'hello']) conn.close()def read(conn): # 在读的进程中通过recv接收对象 data = conn.recv() print(data)if __name__ == '__main__': # Pipe()方法返回一对连接对象 w_conn, r_conn = Pipe() wp = Process(target=write, args=(w_conn,)) rp = Process(target=read, args=(r_conn,)) wp.start() rp.start()# 执行结果# [42, None, 'hello']需要注意的是,两个进程不能同时对一个连接对象进行 send 或 recv 操作。
同步
我们知道线程间的同步是通过锁机制来实现的,进程也一样。
from multiprocessing import Process, Lockimport timedef print_with_lock(l, i): l.acquire() try: time.sleep(1) print('hello world', i) finally: l.release()def print_without_lock(i): time.sleep(1) print('hello world', i)if __name__ == '__main__': lock = Lock() # 先执行有锁的 for num in range(5): Process(target=print_with_lock, args=(lock, num)).start() # 再执行无锁的 # for num in range(5): # Process(target=print_without_lock, args=(num,)).start()有锁的代码将每秒依次打印
hello world 0
hello world 1
hello world 2
hello world 3
hello world 4
如果执行无锁的代码,则在我的电脑上执行结果是这样的
hello worldhello world 0
1
hello world 2
hello world 3
hello world 4
除了 Lock ,还包括 RLock 、 Condition 、 Semaphore 和 Event 等进程间的同步原语。其用法也与线程间的同步原语很类似。 API 使用可以参考文末中引用的文档链接。
在工程中实现进程间的数据共享应当优先使用 队列或管道。
0x02 总结
本文对 multiprocessing 模块中常见的 API 作了简单的介绍。讲述了 Process 和 Pool 的常见用法,同时介绍了进程间的数据方式:队列和管道。最后简单了解了进程间的同步原语。
声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。
Java并发编程系列【未完】:•Java并发编程:核心理论•Java并发编程:Synchronized及其实现原理•Java
python实现文件查找和某些项输出本文是基于给定一文件(students.txt),查找其中GPA分数最高的输出,同时输出其对应的姓名和学分一.思路首先需要打
抢票是并发执行多个进程可以访问同一个文件多个进程共享同一文件,我们可以把文件当数据库,用多个进程模拟多个人执行抢票任务db.txt{"count":1}并发运行
并发编程与多线程编程要了解并发编程,首先要懂得与并行这个概念进行区分。并行是指两个事件同时进行,并发是CPU切换速度快,看起来像是每个任务同时进行一样。多线程是
我们知道,C++和python各有优缺点,C++可以直接映射到硬件底层,实现高效运行,而python能够方便地来进行编程,有助于工程的快速实现。那能不能发挥两者