如何在Python中编写并发程序


Posted in Python onFebruary 27, 2016

GIL

在Python中,由于历史原因(GIL),使得Python中多线程的效果非常不理想.GIL使得任何时刻Python只能利用一个CPU核,并且它的调度算法简单粗暴:多线程中,让每个线程运行一段时间t,然后强行挂起该线程,继而去运行其他线程,如此周而复始,直到所有线程结束.

这使得无法有效利用计算机系统中的"局部性",频繁的线程切换也对缓存不是很友好,造成资源的浪费.

据说Python官方曾经实现了一个去除GIL的Python解释器,但是其效果还不如有GIL的解释器,遂放弃.后来Python官方推出了"利用多进程替代多线程"的方案,在Python3中也有concurrent.futures这样的包,让我们的程序编写可以做到"简单和性能兼得".

多进程/多线程+Queue

一般来说,在Python中编写并发程序的经验是:计算密集型任务使用多进程,IO密集型任务使用多进程或者多线程.另外,因为涉及到资源共享,所以需要同步锁等一系列麻烦的步骤,代码编写不直观.另外一种好的思路是利用多进程/多线程+Queue的方法,可以避免加锁这样麻烦低效的方式.

现在在Python2中利用Queue+多进程的方法来处理一个IO密集型任务.

假设现在需要下载多个网页内容并进行解析,单进程的方式效率很低,所以使用多进程/多线程势在必行.
我们可以先初始化一个tasks队列,里面将要存储的是一系列dest_url,同时开启4个进程向tasks中取任务然后执行,处理结果存储在一个results队列中,最后对results中的结果进行解析.最后关闭两个队列.

下面是一些主要的逻辑代码.

# -*- coding:utf-8 -*-

#IO密集型任务
#多个进程同时下载多个网页
#利用Queue+多进程
#由于是IO密集型,所以同样可以利用threading模块

import multiprocessing

def main():
  tasks = multiprocessing.JoinableQueue()
  results = multiprocessing.Queue()
  cpu_count = multiprocessing.cpu_count() #进程数目==CPU核数目

  create_process(tasks, results, cpu_count)  #主进程马上创建一系列进程,但是由于阻塞队列tasks开始为空,副进程全部被阻塞
  add_tasks(tasks) #开始往tasks中添加任务
  parse(tasks, results) #最后主进程等待其他线程处理完成结果


def create_process(tasks, results, cpu_count):
  for _ in range(cpu_count):
    p = multiprocessing.Process(target=_worker, args=(tasks, results)) #根据_worker创建对应的进程
    p.daemon = True #让所有进程可以随主进程结束而结束
    p.start() #启动

def _worker(tasks, results):
  while True:  #因为前面所有线程都设置了daemon=True,故不会无限循环
    try:
      task = tasks.get()  #如果tasks中没有任务,则阻塞
      result = _download(task)
      results.put(result)  #some exceptions do not handled
    finally:
      tasks.task_done()

def add_tasks(tasks):
  for url in get_urls(): #get_urls() return a urls_list
    tasks.put(url)

def parse(tasks, results):
  try: 
    tasks.join()
  except KeyboardInterrupt as err:
    print "Tasks has been stopped!"
    print err

  while not results.empty():
    _parse(results)



if __name__ == '__main__':
  main()

利用Python3中的concurrent.futures包

在Python3中可以利用concurrent.futures包,编写更加简单易用的多线程/多进程代码.其使用感觉和Java的concurrent框架很相似(借鉴?)
比如下面的简单代码示例

def handler():
  futures = set()

  with concurrent.futures.ProcessPoolExecutor(max_workers=cpu_count) as executor:
    for task in get_task(tasks):
      future = executor.submit(task)
      futures.add(future)

def wait_for(futures):
  try:
    for future in concurrent.futures.as_completed(futures):
      err = futures.exception()
      if not err:
        result = future.result()
      else:
        raise err
  except KeyboardInterrupt as e:
    for future in futures:
      future.cancel()
    print "Task has been canceled!"
    print e
  return result

总结

要是一些大型Python项目也这般编写,那么效率也太低了.在Python中有许多已有的框架使用,使用它们起来更加高效.
但是自己的一些"小打小闹"的程序这样来编写还是不错的.:)

Python 相关文章推荐
在Python中用has_key()方法查找键是否存在的教程
May 21 Python
python爬虫框架scrapy实战之爬取京东商城进阶篇
Apr 24 Python
Python中创建字典的几种方法总结(推荐)
Apr 27 Python
python实现kMeans算法
Dec 21 Python
python中plot实现即时数据动态显示方法
Jun 22 Python
python抓取京东小米8手机配置信息
Nov 13 Python
pygame游戏之旅 调用按钮实现游戏开始功能
Nov 21 Python
Python imutils 填充图片周边为黑色的实现
Jan 19 Python
Python无头爬虫下载文件的实现
Apr 02 Python
python实现简单的tcp 文件下载
Sep 16 Python
python中watchdog文件监控与检测上传功能
Oct 30 Python
python爬虫用scrapy获取影片的实例分析
Nov 23 Python
Python 多线程抓取图片效率对比
Feb 27 #Python
Python 的描述符 descriptor详解
Feb 27 #Python
简析Python的闭包和装饰器
Feb 26 #Python
Android应用开发中Action bar编写的入门教程
Feb 26 #Python
12步教你理解Python装饰器
Feb 25 #Python
Python实现字典依据value排序
Feb 24 #Python
Python中方法链的使用方法
Feb 23 #Python
You might like
解析centos中Apache、php、mysql 默认安装路径
2013/06/25 PHP
PHP实现对png图像进行缩放的方法(支持透明背景)
2015/07/15 PHP
php获取是星期几的的一些常用姿势
2019/12/15 PHP
javascript数组的扩展实现代码集合
2008/06/01 Javascript
包含中国城市的javascript对象实例
2015/08/03 Javascript
IE6兼容透明背景图片及解决方案
2015/08/19 Javascript
jQuery实现的网页竖向菜单效果代码
2015/08/26 Javascript
jQuery+Ajax实现限制查询间隔的方法
2016/06/07 Javascript
Mongoose经常返回e11000 error的原因分析
2017/03/29 Javascript
js设置随机切换背景图片的简单实例
2017/11/12 Javascript
JavaScript累加、迭代、穷举、递归等常用算法实例小结
2018/05/08 Javascript
angular2 NgModel模块的具体使用方法
2019/04/10 Javascript
ckeditor一键排版功能实现方法分析
2020/02/06 Javascript
vue实现简单跑马灯效果
2020/05/25 Javascript
Vue利用localStorage本地缓存使页面刷新验证码不清零功能的实现
2020/09/04 Javascript
微信小程序对图片进行canvas压缩的方法示例详解
2020/11/12 Javascript
[03:40]DOTA2英雄梦之声_第01期_炼金术士
2014/06/23 DOTA
[01:25]DOTA2自定义游戏灵园鬼域等你踏足
2015/10/30 DOTA
[46:28]EG vs Liquid 2019国际邀请赛淘汰赛 败者组 BO3 第二场 8.23
2019/09/05 DOTA
python进阶教程之函数对象(函数也是对象)
2014/08/30 Python
在Python中使用poplib模块收取邮件的教程
2015/04/29 Python
Python函数基础实例详解【函数嵌套,命名空间,函数对象,闭包函数等】
2019/03/30 Python
对python中不同模块(函数、类、变量)的调用详解
2019/07/16 Python
python3.8 微信发送服务器监控报警消息代码实现
2019/11/05 Python
python shapely.geometry.polygon任意两个四边形的IOU计算实例
2020/04/12 Python
基于PyTorch的permute和reshape/view的区别介绍
2020/06/18 Python
Python调用高德API实现批量地址转经纬度并写入表格的功能
2021/01/12 Python
python+opencv实现车道线检测
2021/02/19 Python
Supersmart英国:欧洲市场首批食品补充剂供应商之一
2018/05/05 全球购物
如何判断一段程序是由C 编译程序还是由C++编译程序编译的
2013/08/04 面试题
建筑个人求职信范文
2014/01/25 职场文书
企业管理毕业生求职信
2014/03/11 职场文书
优秀大专毕业生求职信
2014/08/04 职场文书
2014党员自我评议表范文
2014/09/20 职场文书
党的群众路线教育实践活动调研报告
2014/11/03 职场文书
2016年习总书记讲话学习心得体会
2016/01/20 职场文书