如何在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下载FTP上的文件夹的实现代码
Feb 10 Python
Python 获取新浪微博的最新公共微博实例分享
Jul 03 Python
Python使用tablib生成excel文件的简单实现方法
Mar 16 Python
利用python爬取散文网的文章实例教程
Jun 18 Python
将字典转换为DataFrame并进行频次统计的方法
Apr 08 Python
django传值给模板, 再用JS接收并进行操作的实例
May 28 Python
Python pygorithm模块用法示例【常见算法测试】
Aug 16 Python
python tornado修改log输出方式
Nov 18 Python
Django项目基础配置和基本使用过程解析
Nov 25 Python
如何给Python代码进行加密
Jan 10 Python
python openCV实现摄像头获取人脸图片
Aug 20 Python
python实现经典排序算法的示例代码
Feb 07 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
在PHP中使用redis
2013/11/04 PHP
php输出1000以内质数(素数)示例
2014/02/16 PHP
php站内搜索关键词变亮的实现方法
2014/12/30 PHP
php的mail函数发送UTF-8编码中文邮件时标题乱码的解决办法
2015/10/20 PHP
PHP设计模式之工厂模式定义与用法详解
2018/04/03 PHP
定义select的边框颜色
2008/04/28 Javascript
基于Jquery的文字自动截取(提供源代码)
2011/08/09 Javascript
innerText和textContent对比及使用介绍
2013/02/27 Javascript
详解js闭包
2014/09/02 Javascript
使用Javascript简单实现图片无缝滚动
2014/12/05 Javascript
JavaScript实现列表分页功能特效
2015/05/15 Javascript
JavaScript缓冲运动实现方法(2则示例)
2016/01/08 Javascript
浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入
2016/01/19 Javascript
深入解析JavaScript框架Backbone.js中的事件机制
2016/02/14 Javascript
JavaScript中文件上传API详解
2016/04/01 Javascript
js当前页面登录注册框,固定div,底层阴影的实例代码
2016/10/04 Javascript
jquery uploadify隐藏上传进度的实现方法
2017/02/06 Javascript
jQuery中clone()函数实现表单中增加和减少输入项
2017/05/13 jQuery
React为 Vue 引入容器组件和展示组件的教程详解
2018/05/03 Javascript
对vue中v-on绑定自定事件的实例讲解
2018/09/06 Javascript
浅谈Vue 性能优化之深挖数组
2018/12/11 Javascript
vue-test-utils初使用详解
2019/05/23 Javascript
ES6 Class中实现私有属性的一些方法总结
2019/07/08 Javascript
Node.js中console.log()输出彩色字体的方法示例
2019/12/01 Javascript
vue 页面回退mounted函数不执行的解决方案
2020/07/26 Javascript
[01:46]新英雄登场
2019/09/10 DOTA
简单上手Python中装饰器的使用
2015/07/12 Python
对Python3 解析html的几种操作方式小结
2019/02/16 Python
python3.6编写的单元测试示例
2019/08/17 Python
利用html5 canvas破解简单验证码及getImageData接口应用
2013/01/25 HTML / CSS
优秀团支部事迹材料
2014/02/08 职场文书
月度优秀员工获奖感言
2014/08/16 职场文书
护士工作失误检讨书
2014/09/14 职场文书
挂职个人工作总结
2015/03/05 职场文书
Python爬虫之爬取二手房信息
2021/04/27 Python
Hive HQL支持2种查询语句风格
2022/06/25 数据库