如何在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 相关文章推荐
用virtualenv建立多个Python独立虚拟开发环境
Jul 06 Python
分析Python中解析构建数据知识
Jan 20 Python
1分钟快速生成用于网页内容提取的xslt
Feb 23 Python
使用Python的Django和layim实现即时通讯的方法
May 25 Python
python代码编写计算器小程序
Mar 30 Python
pytorch 加载(.pth)格式的模型实例
Aug 20 Python
Python range、enumerate和zip函数用法详解
Sep 11 Python
详解python statistics模块及函数用法
Oct 27 Python
python 画3维轨迹图并进行比较的实例
Dec 06 Python
Python中Yield的基本用法
Oct 18 Python
pytorch简介
Nov 11 Python
Python爬虫之Selenium中frame/iframe表单嵌套页面
Dec 04 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简单系统查询模块代码打包下载
2008/06/07 PHP
PHP 字符串正则替换函数preg_replace使用说明
2011/07/15 PHP
php实现的zip文件内容比较类
2014/09/24 PHP
PHP+APACHE实现网址伪静态
2015/02/22 PHP
php文件读取方法实例分析
2015/06/20 PHP
php实现的统计字数函数定义与使用示例
2017/07/26 PHP
TP5框架实现签到功能的方法分析
2020/04/05 PHP
jquery 常用操作整理 基础入门篇
2009/10/14 Javascript
JavaScript语言精粹经典实例(整理篇)
2016/06/07 Javascript
js实现的页面加载完毕之前loading提示效果完整示例【附demo源码下载】
2016/08/02 Javascript
使用nodejs中httpProxy代理时候出现404异常的解决方法
2016/08/15 NodeJs
jquery实现(textarea)placeholder自动换行
2016/12/22 Javascript
详解Vue 普通对象数据更新与 file 对象数据更新
2017/04/26 Javascript
原生JavaScript来实现对dom元素class的操作方法(推荐)
2017/08/16 Javascript
JS实现点击下拉菜单把选择的内容同步到input输入框内的实例
2018/01/23 Javascript
jQuery高级编程之js对象、json与ajax用法实例分析
2019/11/01 jQuery
原生js拖拽实现图形伸缩效果
2020/02/10 Javascript
vue父子模板传值问题解决方法案例分析
2020/02/26 Javascript
写给新手同学的vuex快速上手指北小结
2020/04/14 Javascript
jQuery实现可以计算进制转换的计算器
2020/10/19 jQuery
[01:03:27]NAVI vs EG 2019国际邀请赛小组赛 BO2 第一场 8.15
2019/08/17 DOTA
Python创建日历实例
2014/08/21 Python
Python中列表和元组的相关语句和方法讲解
2015/08/20 Python
分享Python开发中要注意的十个小贴士
2016/08/30 Python
Python网络爬虫出现乱码问题的解决方法
2017/01/05 Python
Python实现动态添加属性和方法操作示例
2018/07/25 Python
详解Django中间件的5种自定义方法
2018/07/26 Python
Django添加feeds功能的示例
2018/08/07 Python
python常用数据重复项处理方法
2019/11/22 Python
python unichr函数知识点总结
2020/12/16 Python
前端面试必备之CSS3的新特性
2017/09/05 HTML / CSS
KIEHL’S科颜氏官方旗舰店:源自美国的顶级护肤品牌
2018/06/07 全球购物
Sneaker Studio罗马尼亚网站:购买运动鞋
2018/11/04 全球购物
六十岁生日答谢词
2014/01/10 职场文书
个人自我评价范文
2014/02/05 职场文书
Python如何用re模块实现简易tokenizer
2022/05/02 Python