Python 并行计算的那点事1(The Python Concurrency Story)

英文原文:https://powerfulpython.com/blog/python-concurrency-story-pt1/

本文:https://www.cnblogs.com/popapa/p/python_concurrency.html

采集日期:2021-05-02

以编写软件为业有一件事很不错,就是能让人保持谦卑。我一度以为自己很聪明,并对此有点洋洋自得。直到开始每天写代码的日子,才发现并非如此。海森堡 Bug(Heisenbug)就像一个小瘟神一般,静静地等着我狂妄自大的那一刻。。。突然一个 Bug 就被我放了进来,为了找到它花了3个小时,修复却只消1行代码。

当然,对于很多人来说,能让我们保持谦卑的缘由就是并行计算(Concurrency)。从现在开始,无论对并行计算喜欢与否,作为专业的软件工程师,我们都不得不对它做出妥善的考虑和理解。这就需要研发出一些思维模型,能够对其进行清晰地演绎,并且掌握一些为了完成工作所必需的软件工具。理想情况下,还能渐渐学会少发生一些那种找找3小时、修修1行代码的 Bug。

虽然基本原理是通用的,但软件如何实现在很大程度上取决于所用的开发语言。为了实现并行计算,每种开发语言都有着各自的抽象、语法和支持库。本文介绍 Python 的并行计算。。。从某种意义上说,这是一种世界观,用来处理同时发生的多件事情。在21世纪,了解并行计算的来龙去脉能让您获益匪浅。

对并行计算表现能力最强大的可能就是 C 语言了[1]。用 C 语言实现的并行计算,能够真正挑战计算机的物理极限。因为可以利用一些非常底层的系统调用,类似于 Linux 中的clone(),其实那就是用来实现线程的。有了这些利器,相当于掌控了整台虚拟设备!

高级语言通常不会给出那么高的自由度,以便换取很多其他的便利。比如,普通的 PHP 根本不允许创建线程。并且在进程级别上干活的工作量会很大。谢天谢地,现在不是用 PHP 编写代码,而是用 Python,它自带了一套很有意思的并行计算体系。只要完全理解了这套体系,您就会成为全球排名前1%的 Python 程序员。

为了实现上述目标,有必要真正弄明白由现代操作系统提供给 Python 使用的并发原语(Primitive)。理解了这一点,不仅会让您成为更好的 Python 程序员,还将提升您这辈子所有语言的开发水平

太酷了吧!兴奋吗?我就很兴奋。那就开始吧!

(顺便说一句,最让人兴奋的部分是深入探讨何时线程不是真正的线程。您到时候自然就明白了。)

进程和线程(Processes and Threads)


就从基础知识开始吧。现代操作系统为执行线程提供了两种组织方式。进程就是一个正在运行的程序。线程是进程中的活动单位。这就为如何实现并行计算提供了两种基本的选择:N个进程或N个线程。

如果做些深入的了解,线程和进程确实有很多相似之处。其实在 Linux 中,之前提到的系统调用 clone() 既可以用于创建进程,也可以用来创建线程,只要调用函数时提供不同的参数即可。

在实操时,两者的主要区别在于共享和不共享的东西不同。如果一个进程创建了两个子进程,则每个子进程都拥有自己的内存,没有什么共享的东西。(默认如此,有参数可以进行修改。)而新的线程不仅会共享其父进程的内存,还会共享文件描述符、文件系统的上下文和信号处理过程。

采用多线程而不是多进程,有一些实实在在的好处。线程在内存占用方面要轻量得多。同样是守护程序,相比生成10个线程而言,显然生成10个不共享内存的子进程会占用更多的内存空间。此外,线程之间的通信和同步都比进程要简单。根据定义,进程间的任何通信都要用到 IPC 调用,因此还会带来切入内核态的开销。当然可以在进程之间共享内存,但工作量要比线程多些[2]

线程的悲哀(The Tragedy of Threads)


简而言之,在多线程和多进程这两个并行模型中,理论上用线程可以写出性能更高的应用程序。

哦哦,我说的只是“理论上”吧?没错。我们会被带到沟里去:编写无错的多线程代码非常困难。您会遭遇各种微妙的、令人困惑的 Bug,想要很容易就重现这些 Bug 需要靠运气,不走运的话就难了。竞态条件(Race Condition)、死锁(Deadlock)、活锁(Live Lock)等等,还有很多。

解决这些问题的代价就是耗费开发时间。安全的线程编程涉及到规范地使用同步原语(Synchronization Primitive),诸如锁和互斥锁(Mutex)。作为一名优秀的软件工程师,这是应付不时之需的工具包(如果您还没有的话)。但不必这么做总还是最好的。

(等等,这是前兆吗?我觉得是吧。。。)

此外还需要考虑一点,也是 Python 所特有的:Python 的线程并不完全像它看上去的那样。

何时线程不是真正的线程(When Threads Aren't Really Threads)


上述线程实际上指的是操作系统线程(OS Thread)。在用 C 语言编写程序时,调用pthread_create(OS X,Linux)或CreateThread(Windows)即可获得这种线程。这是真正的线程,是由操作系统内核分配和管理的。但如果用 Python 这种高级语言创建线程,就不一定了,至少不完全是。

在较新版的 Python 中,只要线创建threading.Thread的实例,然后调用其start()方法即可启动线程。已启动的线程确实分到了一个独立的操作系统线程,在大多数平台上确实如此[3]。两者的区别在于:两个操作系统线程可以同时运行,以充分利用各自独立的核心或 CPU。但一般情况下两个 Python 线程却无法同时运行。

这是全局解释器锁导致的,也即 GIL。标准 Python 中存在一种机制,同时只允许1个线程运行 Python 字节码[4]。即便是在128核的野兽级机器上运行,标准的多线程 Python 程序在任一时刻也只能用到其中1个核。[5]

乍一看这似乎很糟糕,但事实证明,对于大部分工程领域而言,GIL 根本就不算什么重大限制。就纯粹的 CPU 性能而言[6],Python 的线程确实有些不如操作系统线程。不过 Python 进程完全不受 GIL 的影响,进程就是我们的出路,也是拯救处理器受限(CPU-Bound)任务的出路。第2部分将会讨论进程。


  1. 其实汇编语言的表现能力更强。不过当前很少有人需要用到这么底层的东西了,在可能遭遇的情形下,C 的并行计算能力几乎一样强大。

  2. 在较新的 Linux 中,我知道的最佳方案是mmap(),当然其他几种机制也是可行的。

  3. 某些内核不支持线程的操作系统除外。我希望您不必为此担心。

  4. 引入 GIL 是一个非常好的决定。这样解释器的实现难度就降低了数量级,同时在通常的单线程情况下又维持住了性能。别忘了,Python 是由志愿者提供的。

  5. 其实这只说对了大约98%。解决方案有很多,具体取决于您愿意投入多大的精力。用 C 或 C++编写的扩展模块可以临时释放 GIL。因此,像 numpy 这样的软件包并受单核运行的限制。

    而且,至少还有其他 Python 解释器(Jython、IronPython、PyPy)具有实验性的不带 GIL 的分支(Branch)发布。不过对于您实际编写的纯 Python 代码,超过99%都只能完全用足一个 CPU 核。

  6. 当然,那些并没有接近 CPU 极限的线程,可以采用一些非常有用的编程模式,因此 GIL 甚至都无需多虑。建立一个响应式(Responsive)UI 就是个很好的例子:主线程可以迅速地卖力干活,而辅助线程则负责监测用户的输入、停止计算或执行其他操作。

Python 并行计算那点事 -- 译文 [原创]的更多相关文章

  1. FocusBI: 使用Python爬虫为BI准备数据源(原创)

    关注微信公众号:FocusBI 查看更多文章:加QQ群:808774277 获取学习资料和一起探讨问题. <商业智能教程>pdf下载地址 链接:https://pan.baidu.com/ ...

  2. python并行计算之mpi4py的安装与基本使用

    技术背景 在之前的博客中我们介绍过concurrent等python多进程任务的方案,而之所以我们又在考虑MPI等方案来实现python并行计算的原因,其实是将python的计算任务与并行计算的任务调 ...

  3. PEP 324 subprocess 新的进程模块 -- Python官方文档译文 [原创]

    PEP 324 -- subprocess 新的进程模块(subprocess - New process module) 英文原文:https://www.python.org/dev/peps/p ...

  4. python 爬取糗事百科 gui小程序

    前言:有时候无聊看一些搞笑的段子,糗事百科还是个不错的网站,所以就想用Python来玩一下.也比较简单,就写出来分享一下.嘿嘿 环境:Python 2.7 + win7 现在开始,打开糗事百科网站,先 ...

  5. python scrapy实战糗事百科保存到json文件里

    编写qsbk_spider.py爬虫文件 # -*- coding: utf-8 -*- import scrapy from qsbk.items import QsbkItem from scra ...

  6. python 并行计算

    一.进程和线程 原文链接:https://zhuanlan.zhihu.com/p/356220352 进程是分配资源的最小单位,线程是系统调度的最小单位.当应用程序运行时最少会开启一个进程,此时计算 ...

  7. Python爬取糗事百科

    import urllib import urllib.request from bs4 import BeautifulSoup """     1.抓取糗事百科所有纯 ...

  8. Python扩展方法一二事

    前言 跟着一个有强迫症的老板干活是一件极其幸福的事情(你懂的).最近碰到一个问题,简单的说就是对一个对象做出部分修改后仍然返回此对象,于是我就写了一个方法,老板看了之后只有一句话:不雅观,改成直接对此 ...

  9. python编码的那些事

    字符串编码在python里是经常会遇到的问题,特别是写文件或是网络传输调用某些函数的时候. 现在来看看python中的unicode编码和utf-8编码 字符串编码的历史 计算机只能处理数字,文本转换 ...

随机推荐

  1. PowerDesigner 设计数据库中常用脚本

    PowerDesigner 设计数据库中常用脚本 数据库设计 物理模型设置 Name转Comment脚本 '********************************************** ...

  2. 防数据泄露_MySQL库和数据安全

    目录 攻击场景 外部入侵 内部盗取 防御体系建设 参考 在企业安全建设中有一个方向是防数据泄露,其中一块工作就是保障数据库安全,毕竟这里是数据的源头.当然数据库也分不同的种类,不同类型的数据库的防护手 ...

  3. MySQL深入研究--学习总结(3)

    前言 接上文,继续学习后续章节.细心的同学已经发现,我整理的并不一定是作者讲的内容,更多是结合自己的理解,加以阐述,所以建议结合原文一起理解. 第九章<普通索引和唯一索引,如何选择> 从查 ...

  4. 免费报表工具 积木报表(JiMuReport)的安装

    分享一b/s报表工具(服务),积木报表(JiMuReport),张代浩大佬出品. 官网:http://www.jimureport.com/ 离线版官方下载:https://github.com/zh ...

  5. 前端学习 node 快速入门 系列 —— npm

    其他章节请看: 前端学习 node 快速入门 系列 npm npm 是什么 npm 是 node 的包管理器,绝大多数 javascript 相关的包都放在 npm 上. 所谓包,就是别人提供出来供他 ...

  6. Redis之数据类型和持久化及高可用

    数据类型 Redis支持五种数据类型:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合). String(字符串) String是r ...

  7. 策略模式在PHP业务代码的实践

    [大话设计模式]-- 策略者模式(Strategy):它定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变法,不会影响到使用算法的客户. 策略模式的核心就是屏蔽内部策略算法,内部的 ...

  8. Banner信息扫描

    Banner信息扫描 Banner一般用于表示对用户的欢迎,但其中可能包含敏感信息.获取Banner也属于信息搜索的范畴.在渗透测试中,典型的4xx.5xx信息泄露就属于Banner泄露的一种.在Ba ...

  9. Java中的名称命名规范:

    Java中的名称命名规范:(不遵守,也不会出现编译的错误) 包名:多单词组成时所有字母都小写:xxxyyyzzz 类名.接口名:多单词组成时,所有单词的首字母大写:XxxYyyZzz 变量名.方法名: ...

  10. python列表,元组,字典,集合的比较总结

    这四个都是python中的序列,用于存放数据,他们区别总结如下:   列表list 元组tuple 字典dictionary 集合set 是否可变 可变 不可变 可变 可变 是否有序 有序 有序 无序 ...