Python 多核并行计算的示例代码


Posted in Python onNovember 07, 2017

以前写点小程序其实根本不在乎并行,单核跑跑也没什么问题,而且我的电脑也只有双核四个超线程(下面就统称核好了),觉得去折腾并行没啥意义(除非在做IO密集型任务)。然后自从用上了32核128GB内存,看到 htop 里面一堆空载的核,很自然地就会想这个并行必须去折腾一下。后面发现,其实 Python 的并行真的非常简单。

Python 多核并行计算的示例代码

multiprocessing vs threading

Python 自带的库又全又好用,这是我特别喜欢 Python 的原因之一。Python 里面有 multiprocessing和 threading 这两个用来实现并行的库。用线程应该是很自然的想法,毕竟(直觉上)开销小,还有共享内存的福利,而且在其他语言里面线程用的确实是非常频繁。然而,我可以很负责任的说,如果你用的是 CPython 实现,那么用了 threading 就等同于和并行计算说再见了(实际上,甚至会比单线程更慢),除非这是个IO密集型的任务。

GIL

CPython 指的是 python.org 提供的 Python 实现。是的,Python 是一门语言,它有各种不同的实现,比如 PyPy, Jython, IronPython 等等……我们用的最多的就是 CPython,它几乎就和 Python 画上了等号。

CPython 的实现中,使用了 GIL 即全局锁,来简化解释器的实现,使得解释器每次只执行一个线程中的字节码。也就是说,除非是在等待IO操作,否则 CPython 的多线程就是彻底的谎言!

有关 GIL 下面两个资料写的挺好的:

  1. http://cenalulu.github.io/python/gil-in-python/
  2. http://www.dabeaz.com/python/UnderstandingGIL.pdf

multiprocessing.Pool

因为 GIL 的缘故 threading 不能用,那么我们就好好研究研究 multiprocessing。(当然,如果你说你不用 CPython,没有 GIL 的问题,那也是极佳的。)

首先介绍一个简单粗暴,非常实用的工具,就是 multiprocessing.Pool。如果你的任务能用 ys = map(f, xs) 来解决,大家可能都知道,这样的形式天生就是最容易并行的,那么在 Python 里面并行计算这个任务真是再简单不过了。举个例子,把每个数都平方:

import multiprocessing

def f(x):
  return x * x

cores = multiprocessing.cpu_count()
pool = multiprocessing.Pool(processes=cores)
xs = range(5)

# method 1: map
print pool.map(f, xs) # prints [0, 1, 4, 9, 16]

# method 2: imap
for y in pool.imap(f, xs):
  print y      # 0, 1, 4, 9, 16, respectively

# method 3: imap_unordered
for y in pool.imap_unordered(f, xs):
  print(y)      # may be in any order

map 直接返回列表,而 i 开头的两个函数返回的是迭代器;imap_unordered 返回的是无序的。

当计算时间比较长的时候,我们可能想要加上一个进度条,这个时候 i 系列的好处就体现出来了。另外,有一个小技巧,就是输出 \r 可以使得光标回到行首而不换行,这样就可以制作简易的进度条了。

cnt = 0
for _ in pool.imap_unordered(f, xs):
  sys.stdout.write('done %d/%d\r' % (cnt, len(xs)))
  cnt += 1

更复杂的操作

要进行更复杂的操作,可以直接使用 multiprocessing.Process 对象。要在进程间通信可以使用:

  1. multiprocessing.Pipe
  2. multiprocessing.Queue
  3. 同步原语
  4. 共享变量

其中我强烈推荐的就是 Queue,因为其实很多场景就是生产者消费者模型,这个时候用 Queue 就解决问题了。用的方法也很简单,现在父进程创建 Queue,然后把它当做 args 或者 kwargs 传给 Process 就好了。

使用 Theano 或者 Tensorflow 等工具时的注意事项

需要注意的是,在 import theano 或者 import tensorflow 等调用了 Cuda 的工具的时候会产生一些副作用,这些副作用会原样拷贝到子进程中,然后就发生错误,如:

could not retrieve CUDA device count: CUDA_ERROR_NOT_INITIALIZED

解决的方法是,保证父进程不引入这些工具,而是在子进程创建好了以后,让子进程各自引入。

如果使用 Process,那就在 target 函数里面 import。举个例子:

import multiprocessing

def hello(taskq, resultq):
  import tensorflow as tf
  config = tf.ConfigProto()
  config.gpu_options.allow_growth=True
  sess = tf.Session(config=config)
  while True:
    name = taskq.get()
    res = sess.run(tf.constant('hello ' + name))
    resultq.put(res)

if __name__ == '__main__':
  taskq = multiprocessing.Queue()
  resultq = multiprocessing.Queue()
  p = multiprocessing.Process(target=hello, args=(taskq, resultq))
  p.start()

  taskq.put('world')
  taskq.put('abcdabcd987')
  taskq.close()

  print(resultq.get())
  print(resultq.get())

  p.terminate()
  p.join()

如果使用 Pool,那么可以编写一个函数,在这个函数里面 import,并且把这个函数作为 initializer传入到 Pool 的构造函数里面。举个例子:

import multiprocessing

def init():
  global tf
  global sess
  import tensorflow as tf
  config = tf.ConfigProto()
  config.gpu_options.allow_growth=True
  sess = tf.Session(config=config)

def hello(name):
  return sess.run(tf.constant('hello ' + name))

if __name__ == '__main__':
  pool = multiprocessing.Pool(processes=2, initializer=init)
  xs = ['world', 'abcdabcd987', 'Lequn Chen']
  print pool.map(hello, xs)

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Python 相关文章推荐
Django实现全文检索的方法(支持中文)
May 14 Python
win7 x64系统中安装Scrapy的方法
Nov 18 Python
Python实现的KMeans聚类算法实例分析
Dec 29 Python
selenium 安装与chromedriver安装的方法步骤
Jun 12 Python
简单了解django缓存方式及配置
Jul 19 Python
Python 使用matplotlib模块模拟掷骰子
Aug 08 Python
Python面向对象程序设计之私有变量,私有方法原理与用法分析
Mar 23 Python
Python Scrapy框架:通用爬虫之CrawlSpider用法简单示例
Apr 11 Python
python编写实现抽奖器
Sep 10 Python
Python约瑟夫生者死者小游戏实例讲解
Jan 04 Python
Python爬虫之用Xpath获取关键标签实现自动评论盖楼抽奖(二)
Jun 07 Python
Python 视频画质增强
Apr 28 Python
python判断字符串是否是json格式方法分享
Nov 07 #Python
python好玩的项目—色情图片识别代码分享
Nov 07 #Python
深入理解python中函数传递参数是值传递还是引用传递
Nov 07 #Python
python中numpy.zeros(np.zeros)的使用方法
Nov 07 #Python
django项目运行因中文而乱码报错的几种情况解决
Nov 07 #Python
Python创建二维数组实例(关于list的一个小坑)
Nov 07 #Python
python 简单备份文件脚本v1.0的实例
Nov 06 #Python
You might like
php获取本周星期一具体日期的方法
2015/04/20 PHP
php生成数字字母的验证码图片
2015/07/14 PHP
CentOS下与Apache连接的PHP多版本共存方案实现详解
2015/12/19 PHP
php版微信公众平台接口参数调试实现判断用户行为的方法
2016/09/23 PHP
Discuz论坛密码与密保加密规则
2016/12/19 PHP
PHPExcel导出2003和2007的excel文档功能示例
2017/01/04 PHP
PHP获取MySQL执行sql语句的查询时间方法
2018/08/21 PHP
PHP 获取客户端 IP 地址的方法实例代码
2018/11/11 PHP
PHP bin2hex()函数基础实例讲解
2019/02/11 PHP
Javascript 读后台cookie代码
2008/09/15 Javascript
全面解析Bootstrap排版使用方法(标题)
2015/11/30 Javascript
基于JavaScript实现弹出框效果
2016/02/19 Javascript
利用策略模式与装饰模式扩展JavaScript表单验证功能
2017/02/14 Javascript
基于nodejs res.end和res.send的区别
2018/05/14 NodeJs
vue.js中proxyTable 转发请求的实现方法
2018/09/20 Javascript
js中int和string数据类型互相转化实例
2019/01/16 Javascript
layui use 定义js外部引用函数的方法
2019/09/26 Javascript
在Django框架中运行Python应用全攻略
2015/07/17 Python
Python 利用内置set函数对字符串和列表进行去重的方法
2018/06/29 Python
Django开发中的日志输出的方法
2018/07/02 Python
Python对象中__del__方法起作用的条件详解
2018/11/01 Python
Django csrf 两种方法设置form的实例
2019/02/03 Python
详解python播放音频的三种方法
2019/09/23 Python
python GUI库图形界面开发之pyinstaller打包python程序为exe安装文件
2020/02/26 Python
Python configparser模块操作代码实例
2020/06/08 Python
python 使用多线程创建一个Buffer缓存器的实现思路
2020/07/02 Python
python 实现图片裁剪小工具
2021/02/02 Python
美国最大的袜子制造商和零售商:Renfro Socks
2017/09/03 全球购物
乐天旅游香港网站:日本饭店预订
2017/11/29 全球购物
Lentiamo荷兰:在线订购隐形眼镜、隐形眼镜液和太阳镜
2019/10/25 全球购物
给交警的表扬信
2014/01/12 职场文书
会议欢迎词范文
2015/01/27 职场文书
兼职安全员岗位职责
2015/02/15 职场文书
公文写作:工伤事故分析报告怎么写?
2019/11/05 职场文书
斗罗大陆八大特殊魂兽,龙族始祖排榜首,第五最残忍(翠魔鸟)
2022/03/18 国漫
《王者天下》第4季首话新剧照 4月9日正式开播
2022/04/07 日漫