64

尹立博:Python 全局解释器锁与并发 | AI 研习社第 59 期猿桌会

 5 years ago
source link: https://www.leiphone.com/news/201811/eqsXiATXYIGmbahP.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

雷锋网 AI 科技评论按:作为排名靠前的最受欢迎和增长最快的编程语言之一,Python 是一种多用途、高级别、面向对象、交互式、解释型和对用户非常友好的编程语言,拥有卓越的可读性和极高的自由度。而为了能利用多核多线程的的优势,同时又要保证线程之间数据完整性和状态同步,Python 官方的、最广泛使用的解释器——CPython 往往会采取最简单的加锁的方式——全局解释器锁(GIL)。

然而, GIL 的设计有时会显得笨拙低效,并对语言的并发性带来严重限制,但是此时由于内置库和第三方库已经对 GIL 形成了巨大的依赖,想改变 GIL 反而变得困难了。 不过实际上,Python 生态系统中存在诸多工具可以解决这一问题。

近日,在雷锋网 (公众号:雷锋网) AI 研习社公开课上,毕业于澳大利亚国立大学的尹立博介绍了全局解释器锁(GIL)和提升并发性的不同思路。公开课回放视频网址: http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

尹立博:毕业于西澳大利亚大学和澳大利亚国立大学。现在堪培拉 Seeing Machines 公司担任数据分析师,日常使用 Python 数据工具对大量时序数据进行管理、分析与可视化开发。

分享主题: Python 全局解释器锁与并发

分享提纲:

1、全局解释器锁 (GIL)

2、多进程 (multiprocessing)

3、多线程 (multithreading)

4、异步 (async)

5、分布式计算(以 Dask 为例)

雷锋网 AI 研习社将其分享内容整理如下:

今天要跟大家分享的是 Python 全局解释器锁与并发。我会先介绍一下全局解释器锁 (GIL))的概念和影响;接下来会借助几个案例分析来展示 Python 通过多进程、多线程和异步、分布式计算来达成并发的几种方式;最后会介绍一套分布式计算工具——Dask。

全局解释器锁 (GIL)

GIL 的概念用简单的一句话来解释,就是「任一时刻,无论线程多少,单一 CPython 解释器只能执行一条字节码」。这个定义需要注意的点包括:

第一,GIL 不属于 Python 语言定义,而是 CPython 解释器实现的一部分;

第二,其他 Python 解释器不一定有 GIL。例如 Jython (JVM) 和 IronPython (CLR) 没有 GIL,而 PyPy 有 GIL;

第三,GIL 并不是 Python 的专利。其他语言也有 GIL,尤其是动态语言,如 Ruby MRI。

说到 GIL,就不得不提 Python 线程模型,它的运行方式如下:

  • CPython 使用 OS 原生线程,由 OS 负责调度;

  • 每个解释器进程有唯一的主线程和用户定义的任意数量子线程;

  • GIL 是字节码层面上的互斥锁。刚刚定义中提到的 PyThread_type_lock 就是 OS 互斥锁的别名

  • 每个解释器进程有且仅有一把锁;

  • 当解释器启动时,主线程即获取 GIL;

  • 一个线程持有 GIL 并执行字节码时,其他线程处于阻塞状态。

GIL 被加到 CPython 解释器中,是有其原因的。在 1992 年,单 CPU 是合理的假设!多核则是 2005-2006 年前后才普及,此外,GIL 的优势还包括:

  • 简化解释器实现;

  • 优化单进程性能;

  • 简化 C 扩展库的整合。

Python 有两种多任务模型:一种叫做协作式 (cooperative) 多任务;另一种叫抢占式 (preemptive) 多任务。

协作式多任务:

  • 在 I/O 前主动释放 GIL,I/O 之后重新获取。这可以在 C 源代码中使用 Py_BEGIN_ALLOW_THREADS / Py_END_ALLOW_THREADS 宏实现

  • 这种多任务方式能够提升代码性能!

抢占式多任务:

  • 间歇性挂起活跃进程,交由 OS 重新调度

  • Python 2:每执行 100 个字节码,当前进程就会被挂起

  • Python 3.2+: 每隔 5 毫秒

  • 这种多任务方式不提高代码性能,但使得多个任务能在同一时间段内执行

接下来可以进展到去除 GIL。这是很多 Python 用户十分期待的事情,但是短期内是不太可能实现的,它的难点包括:

第一,技术问题

  • Guido 要求不降低单线程执行效率

  • 兼容现有引用计数与垃圾回收机制

  • 兼容现有 C 扩展

第二,在社区友好性上,不显著提高开发难度。

尽管如此,我们也可以看到一些现有去除 GIL 的实验性的方案:

  • Gilectomy:尝试将 GIL 换成若干小锁,然而这种方案严重降低了 Python 的性能。首先,它会使得多线程竞争同一把锁。其次,它在将 GIL 换成若干小锁后,将严重降低缓存的命中率。

  • PyPy:实验性分支支持软件事务内存 (STM),不过 STM 目前还是一个相对少见的机制,可解决当前很多问题,但是实现非常困难——尤其在像 Python 这种高度动态的语言当中。

  • Starlark:这种方案并非去掉 GIL,而是一门兼容部分 Python 语法,并发执行字节码的新语言。它目前用于 Google Bazel 编译系统,我个人认为这是一个非常有意思的未来趋势。

既然现在去除 GIL 的方案都有很多弊端,并且短期内我们也无法让 GIL 从 Python 中被去除,我们最常见的解决方案就是避开 GIL,主要通过两种手段实现:

第一种是多解释器进程并发 (multiprocessing)

第二种是避免执行 Python 字节码,常见的方法有:Cython ctypes、部分 NumPy 函数释放 GIL、Numba JIT「nogil=True」,以及 TensorFlow/PyTorch JIT。

多进程(multiprocessing)和多线程(multithreading)

进入案例分析前,先介绍几个相关的概念。

首先介绍一下并行与并发的区别:

  • 并发(concurrency):是指多个操作可以在重叠的时间段内进行,例如在第一个时间片内,线程 A 执行,线程 B 阻塞;第二个时间片内,线程 B 等待 I/O,而线程 A 执行;第三个时间片内,线程 A 执行,而线程 B 还在等待 I/O。

  • 并行(parallelism):是指多个操作在同一时间点上进行。无论在哪个时间片里,两个线程可能同时处于某一状态。例如在第一个时间片内,线程 A 执行,线程 B 执行;第二个时间片内,线程 A 等待 I/O,线程 B 也在等待 I/O ;第三个时间片内,线程 A 执行,而线程 B 也 执行。

多线程意味着我们在使用并发这种线程模型,而多进程则是在使用并行这一线程模型,其各有利弊:

  • 多线程并发的优势为:可共享内存空间,方便交换数据;劣势为:会同时写入内存将导致数据损坏。

  • 多进程并行的优势为:内存空间独立(恰来自其劣势);劣势为:进程间交互需要序列化-通信-反序列化。

接下来我们将通过一个案例来尝试 Python 并发的几种不同解决方案的案例:

mEryUbi.png!web

(关于尝试 Python 并发的几种不同解决方案的案例讲解,请回看视频 00:19:05 处, http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

这就讲到多进程(multiprocessing)这一概念,它的适用场景包括:

  • CPU 占用率高

  • 子进程间通信简单

  • 相关变量和函数可被序列化,但占用内存较小

如果想知道更多内容,大家可参见文档:

https://docs.python.org/3/library/multiprocessing.html

https://docs.python.org/3/library/concurrent.futures.html

接下来进入到多进程解决方案的案例讲解:

7B7Nvab.png!web

(关于多进程解决方案的案例讲解,请回看视频 00:23:25 处, http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

之后要讲到多线程 (multithreading),多线程的使用场景包括:

  • CPU 占用率低

  • I/O 负载高

  • 子任务需要共享内存

如要了解更多内容,可以参见文档:

https://docs.python.org/3/library/threading.html

https://docs.python.org/3/library/queue.html

IBZNFzE.png!web

(关于多线程解决方案的案例讲解,请回看视频 00:33:25 处, http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

再看一下 Python 多线程编程难点,下面这些难点有些针对 Python,有些是所有多线程共通的难题:

第一,CPython 的线程切换可能在任意字节码之间发生,而 Python 指令不具有原子性

第二,每次访问受限资源都需获取锁

第三,锁不具有强制性,即使忘记获取锁,代码也可能运行

第四,竞争状态难以复制

我们看一个相关的案例——多线程计数器:

VbmABvZ.png!web

(关于多线程计数器的案例讲解,请回看视频 00:37:00 处, http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

异步 (async)

接着讲一下异步 (async)。Python 中的异步是一种在单一线程内使用生成器实现的协程,比线程能更高效地组织非阻塞式任务。协程的切换由 Python 解释器内完成。当然,其他语言也有异步编程,比如 Go 语言的 goroutine,以及 Nginx 用 C 实现了异步编程。

关于更多异步编程的内容,大家可参见文档:

https://docs.python.org/3/library/asyncio-task.html

看案例之前,先比较一下异步与线程。与线程相比,异步的优劣势分别为:

优势:

  • 简单的多任务模型

  • 明确的协程切换点

  • 系统开销远小于 OS 原生线程

劣势:

  • 有相对独立的生态系统

  • 与其他并发模型混用较难

  • API 仍未稳定

下面我们看异步的案例:

ZnqmIza.png!web

(关于异步的案例讲解,请回看视频 00:46:05 处, http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

分布式计算(以 Dask 为例)

最后讲一下分布式计算,本堂课中的分布式计算以 Dask 为例。

Dask 是一种基于运算图的动态任务调度器,可使用动态调度器扩展 NumPy 和 Pandas。左边这个图就是 Dask 的运算图。

f2yyuyv.png!web

(关于 Dask 运算图的讲解,请回看视频 00:55:45 处, http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

与另一种分布式计算方法 Spark 比较,Dask 的特性非常鲜明:

  • 它是一个纯 Python 实现

  • 无需遵循 map-reduce 范式

  • 细粒调度带来较低的延迟

在 Dask 中,我们更关注的是 Distributed。它是 Dask 在异构集群上的扩展。它的网络结构遵循客户 – 调度器 – 工作节点这样的形式,因此要求所有节点拥有相同的 Python 运行环境。

接下来我们看一个简单的案例:

3aUF7ji.png!web

(关于该案例讲解,请回看视频 00:59:45 处, http://www.mooc.ai/open/course/569?=aitechtalkyinlibo

最后放上今天这堂课涉及到的内容的演讲,基本都能在 youtube 上进行观看。

Dave Beazley: Understanding the Python GIL, PyCon 2010 

https://www.youtube.com/watch?v=Obt-vMVdM8s 

https://www.dabeaz.com/python/UnderstandingGIL.pdf 

Dave Beazley: Embracing the Global Interpreter Lock (GIL), PyCodeConf 2011

https://www.youtube.com/watch?v=fwzPF2JLoeU 

Larry Hastings: Python's Infamous GIL, PyCon 2015 

https://www.youtube.com/watch?v=KVKufdTphKs 

Larry Hastings: Removing Python's GIL: The Gilectomy, PyCon 2016 

https://www.youtube.com/watch?v=P3AyI_u66Bw

A Jesse Jiryu Davis: Grok the GIL Write Fast And Thread Safe Python, PyCon 2017

https://www.youtube.com/watch?v=7SSYhuk5hmc

Raymond Hettinger: Keynote on Concurrency, PyBay 2017

https://www.youtube.com/watch?v=9zinZmE3Ogk 

https://pybay.com/site_media/slides/raymond2017-keynote/index.html 

Dave Beazley: Fear and Awaiting in Async: A Savage Journey to the Heart of the Coroutine Dream

https://www.youtube.com/watch?v=E-1Y4kSsAFc 

Robert Smallshire: Coroutine Concurrency in Python 3 with asyncio 

https://www.youtube.com/watch?v=c5wodlqGK-M •Matthew Rocklin: Dask for ad hoc distributed computing 

https://www.youtube.com/watch?v=EEfI-11itn0 

Matthew Rocklin: Dask: A Pythonic Distributed Data Science Framework, PyCon 2017 

https://www.youtube.com/watch?v=RA_2qdipVng

以上就是本期嘉宾的全部分享内容。更多公开课视频请到雷锋网 AI 研习社社区( http://ai.yanxishe.com/ )观看。关注微信公众号:AI 研习社(okweiwu),可获取最新公开课直播时间预告。

雷锋网原创文章,未经授权禁止转载。详情见 转载须知

32MZrey.jpg!web

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK