Python多进程并发与多线程并发编程实例总结


Posted in Python onFebruary 08, 2018

本文实例总结了Python多进程并发与多线程并发。分享给大家供大家参考,具体如下:

这里对python支持的几种并发方式进行简单的总结。

Python支持的并发分为多线程并发与多进程并发(异步IO本文不涉及)。概念上来说,多进程并发即运行多个独立的程序,优势在于并发处理的任务都由操作系统管理,不足之处在于程序与各进程之间的通信和数据共享不方便;多线程并发则由程序员管理并发处理的任务,这种并发方式可以方便地在线程间共享数据(前提是不能互斥)。Python对多线程和多进程的支持都比一般编程语言更高级,最小化了需要我们完成的工作。

一.多进程并发

Mark Summerfield指出,对于计算密集型程序,多进程并发优于多线程并发。计算密集型程序指的程序的运行时间大部分消耗在CPU的运算处理过程,而硬盘和内存的读写消耗的时间很短;相对地,IO密集型程序指的则是程序的运行时间大部分消耗在硬盘和内存的读写上,CPU的运算时间很短。

对于多进程并发,python支持两种实现方式,一种是采用进程安全的数据结构:multiprocessing.JoinableQueue,这种数据结构自己管理“加锁”的过程,程序员无需担心“死锁”的问题;python还提供了一种更为优雅而高级的实现方式:采用进程池。下面一一介绍。

1.队列实现——使用multiprocessing.JoinableQueue

multiprocessing是python标准库中支持多进程并发的模块,我们这里采用multiprocessing中的数据结构:JoinableQueue,它本质上仍是一个FIFO的队列,它与一般队列(如queue中的Queue)的区别在于它是多进程安全的,这意味着我们不用担心它的互斥和死锁问题。JoinableQueue主要可以用来存放执行的任务和收集任务的执行结果。举例来看(以下皆省去导入包的过程):

def read(q):
  while True:
    try:
      value = q.get()
      print('Get %s from queue.' % value)
      time.sleep(random.random())
    finally:
      q.task_done()
def main():
  q = multiprocessing.JoinableQueue()
  pw1 = multiprocessing.Process(target=read, args=(q,))
  pw2 = multiprocessing.Process(target=read, args=(q,))
  pw1.daemon = True
  pw2.daemon = True
  pw1.start()
  pw2.start()
  for c in [chr(ord('A')+i) for i in range(26)]:
    q.put(c)
  try:
    q.join()
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

对于windows系统的多进程并发,程序文件里必须含有“入口函数”(如main函数),且结尾处必须调用入口点。例如以if __name__ == '__main__': main()结尾。

在这个最简单的多进程并发例子里,我们用多进程实现将26个字母打印出来。首先定义一个存放任务的JoinableQueue对象,然后实例化两个Process对象(每个对象对应一个子进程),实例化Process对象需要传送target和args参数,target是实现每个任务工作中的具体函数,args是target函数的参数。

pw1.daemon = True
pw2.daemon = True

这两句话将子进程设置为守护进程——主进程结束后随之结束。

pw1.start()
pw2.start()

一旦运行到这两句话,子进程就开始独立于父进程运行了,它会在单独的进程里调用target引用的函数——在这里即read函数,它是一个死循环,将参数q中的数一一读取并打印出来。

value = q.get()

这是多进程并发的要点,q是一个JoinableQueue对象,支持get方法读取第一个元素,如果q中没有元素,进程就会阻塞,直至q中被存入新元素。

因此执行完pw1.start() pw2.start()这两句话后,子进程虽然开始运行了,但很快就堵塞住。

for c in [chr(ord('A')+i) for i in range(26)]:
    q.put(c)

将26个字母依次放入JoinableQueue对象中,这时候两个子进程不再阻塞,开始真正地执行任务。两个子进程都用value = q.get()来读取数据,它们都在修改q对象,而我们并不用担心同步问题,这就是multiProcessing.Joinable数据结构的优势所在——它是多进程安全的,它会自动处理“加锁”的过程。

try:
    q.join()

q.join()方法会查询q中的数据是否已读完——这里指的就是任务是否执行完,如果没有,程序会阻塞住等待q中数据读完才开始继续执行(可以用Ctrl+C强制停止)。

对Windows系统,调用任务管理器应该可以看到有多个子进程在运行。

2.进程池实现——使用concurrent.futures.ProcessPoolExecutor

Python还支持一种更为优雅的多进程并发方式,直接看例子:

def read(q):
    print('Get %s from queue.' % q)
    time.sleep(random.random())
def main():
  futures = set()
  with concurrent.futures.ProcessPoolExecutor() as executor:
    for q in (chr(ord('A')+i) for i in range(26)):
      future = executor.submit(read, q)
      futures.add(future)
  try:
    for future in concurrent.futures.as_completed(futures):
      err = future.exception()
      if err is not None:
        raise err
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

这里我们采用concurrent.futures.ProcessPoolExecutor对象,可以把它想象成一个进程池,子进程往里“填”。我们通过submit方法实例一个Future对象,然后把这里Future对象都填到池——futures里,这里futures是一个set对象。只要进程池里有future,就会开始执行任务。这里的read函数更为简单——只是把一个字符打印并休眠一会而已。

try:
    for future in concurrent.futures.as_completed(futures):

这是等待所有子进程都执行完毕。子进程执行过程中可能抛出异常,err = future.exception()可以收集这些异常,便于后期处理。

可以看出用Future对象处理多进程并发更为简洁,无论是target函数的编写、子进程的启动等等,future对象还可以向使用者汇报其状态,也可以汇报执行结果或执行时的异常。

二.多线程并发

对于IO密集型程序,多线程并发可能要优于多进程并发。因为对于网络通信等IO密集型任务来说,决定程序效率的主要是网络延迟,这时候是使用进程还是线程就没有太大关系了。

1.队列实现——使用queue.Queue

程序与多进程基本一致,只是这里我们不必使用multiProcessing.JoinableQueue对象了,一般的队列(来自queue.Queue)就可以满足要求:

def read(q):
  while True:
    try:
      value = q.get()
      print('Get %s from queue.' % value)
      time.sleep(random.random())
    finally:
      q.task_done()
def main():
  q = queue.Queue()
  pw1 = threading.Thread(target=read, args=(q,))
  pw2 = threading.Thread(target=read, args=(q,))
  pw1.daemon = True
  pw2.daemon = True
  pw1.start()
  pw2.start()
  for c in [chr(ord('A')+i) for i in range(26)]:
    q.put(c)
  try:
    q.join()
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

并且这里我们实例化的是Thread对象,而不是Process对象,程序的其余部分看起来与多进程并没有什么两样。

2. 线程池实现——使用concurrent.futures.ThreadPoolExecutor

直接看例子:

def read(q):
    print('Get %s from queue.' % q)
    time.sleep(random.random())
def main():
  futures = set()
  with concurrent.futures.ThreadPoolExecutor(multiprocessing.cpu_count()*4) as executor:
    for q in (chr(ord('A')+i) for i in range(26)):
      future = executor.submit(read, q)
      futures.add(future)
  try:
    for future in concurrent.futures.as_completed(futures):
      err = future.exception()
      if err is not None:
        raise err
  except KeyboardInterrupt:
    print("stopped by hand")
if __name__ == '__main__':
  main()

用ThreadPoolExecutor与用ProcessPoolExecutor看起来没什么区别,只是改了一下签名而已。

不难看出,不管是使用队列还是使用进/线程池,从多进程转化到多线程是十分容易的——仅仅是修改了几个签名而已。当然内部机制完全不同,只是python的封装非常好,使我们可以不用关心这些细节,这正是python优雅之处。

希望本文所述对大家Python程序设计有所帮助。

Python 相关文章推荐
python使用WMI检测windows系统信息、硬盘信息、网卡信息的方法
May 15 Python
python+matplotlib绘制饼图散点图实例代码
Jan 20 Python
python实现txt文件格式转换为arff格式
May 31 Python
Django csrf 两种方法设置form的实例
Feb 03 Python
python中aioysql(异步操作MySQL)的方法
Apr 11 Python
python网络应用开发知识点浅析
May 28 Python
Python3.7 pyodbc完美配置访问access数据库
Oct 03 Python
Python操作Sonqube API获取检测结果并打印过程解析
Nov 27 Python
pytorch方法测试——激活函数(ReLU)详解
Jan 15 Python
python利用opencv保存、播放视频
Nov 02 Python
Python爬虫制作翻译程序的示例代码
Feb 22 Python
python如何利用traceback获取详细的异常信息
Jun 05 Python
Python的CGIHTTPServer交互实现详解
Feb 08 #Python
Python获取CPU、内存使用率以及网络使用状态代码
Feb 08 #Python
python实现二叉查找树实例代码
Feb 08 #Python
单链表反转python实现代码示例
Feb 08 #Python
Python测试人员需要掌握的知识
Feb 08 #Python
python实现单向链表详解
Feb 08 #Python
Python生成器以及应用实例解析
Feb 08 #Python
You might like
鸡肋的PHP单例模式应用详解
2013/06/03 PHP
php实现留言板功能(代码详解)
2017/03/28 PHP
phpStudy配置多站点多域名方法及遇到的403错误解决方法
2017/10/19 PHP
phpstudy隐藏index.php的方法
2020/09/21 PHP
改进:论坛UBB代码自动插入方式
2006/12/22 Javascript
IE浏览器打印的页眉页脚设置解决方法
2009/12/08 Javascript
Jquery截取中文字符串的实现代码
2010/12/22 Javascript
JQuery入门——事件切换之toggle()方法应用介绍
2013/02/05 Javascript
js Date概念详细介绍
2013/11/22 Javascript
用jquery实现的一个超级简单的下拉菜单
2014/05/18 Javascript
jquery实现页面百叶窗走马灯式翻滚显示效果的方法
2015/03/12 Javascript
javascript实现多栏闭合展开式广告位菜单效果实例
2015/08/05 Javascript
jQuery实现获取元素索引值index的方法
2016/09/18 Javascript
简单理解vue中track-by属性
2016/10/26 Javascript
Vue.js学习之计算属性
2017/01/22 Javascript
jquery实现提示语淡入效果
2017/05/05 jQuery
js数字滑动时钟的简单实现(示例讲解)
2017/08/14 Javascript
jQuery条件分页 代替离线查询(附代码)
2017/08/17 jQuery
JavaScript定义及输出螺旋矩阵的方法详解
2017/12/01 Javascript
jQuery 实现左右两侧菜单添加、移除功能
2018/01/02 jQuery
详解vue-loader在项目中是如何配置的
2018/06/04 Javascript
微信公众号网页分享功能开发的示例代码
2020/05/27 Javascript
[34:47]完美世界DOTA2联赛PWL S2 Magma vs LBZS 第一场 11.18
2020/11/18 DOTA
pydev使用wxpython找不到路径的解决方法
2013/02/10 Python
Python3基础之基本运算符概述
2014/08/13 Python
Python工程师面试必备25条知识点
2018/01/17 Python
详谈Pandas中iloc和loc以及ix的区别
2018/06/08 Python
pyqt5利用pyqtDesigner实现登录界面
2019/03/28 Python
Django基础三之视图函数的使用方法
2019/07/18 Python
真正了解CSS3背景下的@font face规则
2017/05/04 HTML / CSS
请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
2015/07/16 面试题
研讨会主持词
2014/04/02 职场文书
大学生简短的自我评价
2014/09/12 职场文书
个人委托书怎么写
2014/09/17 职场文书
学校开学标语
2014/10/06 职场文书
Python GUI编程之tkinter 关于 ttkbootstrap 的使用详解
2022/03/03 Python