Python 的多线程与 GIL

Python的多线程与GIL

Python从0.9.8版就开始支持多线程( 模块),1.5.1版引入了 高级模块,是对thread模块的封装。

在Python3中, 模块被重命名为 ,强调其作为底层模块应该避免使用。

模块对应源码在 中,我们看下 模块提供的接口函数:

0x00 线程的创建

[1] 创建bootstate结构boot并初始化。

bootstate结构体记录了线程的信息:

, , 记录线程执行的函数以及参数。 和 分别保存了进程状态 对象和线程状态 对象。

通过 获取当前线程状态 对象,进而可以获取进程状态 对象。

定义在 :

通过 函数创建线程状态 对象,定义在 :

[2] 初始化多线程环境

当Python启动时,是并不支持多线程的。换句话说,Python中支持多线程的数据结构以及GIL都是没有创建的,Python之所以有这种行为是因为大多数的Python程序都不需要多线程的支持。Python选择了让用户激活多线程机制的策略。在Python虚拟机启动时,多线程机制并没有被激活,它只支持单线程,一旦用户调用thread.startnewthread,明确指示Python虚拟机创建新的线程,Python就能意识到用户需要多线程的支持,这个时候,Python虚拟机会自动建立多线程机制需要的数据结构、环境以及那个至关重要的GIL。摘自:《Python源码剖析》 — 陈儒

函数定义在 :

[A] 检查是否存在GIL

函数定义在 :

是一组原子操作,封装了不同的平台的原子接口。

定义在 :

函数是c11标准的原子读操作,参考Atomic operations library。

是枚举元素:

指定常规的非原子内存访问如何围绕原子操作排序,这边我们不细究这些原子操作排序的不同,感兴趣可以参考memory order。

我们只关心两种原子操作,一种 原子读和一种 原子写。

是一个 对象,表示Python运行状态对象。 指向 对象,表示Python字节码执行器的运行状态对象,定义在 :

指向 对象,表示GIL的运行状态对象,定义在 :

就是传说中的GIL,是一个 对象,定义在 :

所以, 函数通过原子读操作检查原子位 是否初始化。未初始化时, 为-1。当GIL被线程获取时, 为1,反之为0。

[B] 创建并初始化GIL

结构体中的 和 用来保护 。

The GIL is just a boolean variable (locked) whose access is protectedby a mutex (gilmutex), and whose changes are signalled by a conditionvariable (gilcond). gil_mutex is taken for short periods of time,and therefore mostly uncontended.

GIL利用条件机制和互斥锁 保护一个锁变量 作为实现。

和 定义在 ,根据平台不一样具体实现也不一样,以POSIX Thread(pthread)为例:

函数初始化锁变量 和初始化条件变量 之后,通过 原子写初始化 , 是一个 对象,定义在 :

是一个原子类型指针,当一个线程获得GIL之后, 将指向线程状态 对象。

并通过 原子写初始化 。

[C] 主线程获取GIL

函数定义在 :

首先获取mutex互斥锁,获取mutex互斥锁之后检查是否有线程持有GIL( 为1),如果GIL被占有的话,设置cond信号量等待。等待的时候线程就会挂起,释放mutex互斥锁。cond信号被唤醒时,mutex互斥锁会自动加锁。while语句避免了意外唤醒时条件不满足,大多数情况下都是条件满足被唤醒。

如果等待超时并且这期间没有发生线程切换,就通过 请求持有GIL的线程释放GIL。反之获得GIL就将 置为1,并将当前线程状态 对象保存到 , 切换次数加1。

[D] 检查和初始化原生系统线程环境

是一个 结构对象:

实现了一个机制:

Mechanism whereby asynchronously executing callbacks (e.g. UNIXsignal handlers or Mac I/O completion routines) can schedule callsto a function to be called synchronously.

也就是主线程执行一些被注册的函数, 记录了主线程id:

获得线程id之前会检查 变量,如果说GIL指示着Python的多线程环境是否已经建立,那么这个 变量就指示着为了使用底层平台所提供的原生thread,必须的初始化动作是否完成。

和 定义在 :

POSIX Thread(pthread) 下 会根据编译器和平台来确认是否要进行初始化动作,定义在 :

用来确保线程安全,是一个Python级别的线程锁。

[3] 创建原生系统线程

POSIX Thread(pthread) 下的 定义在 :

函数通过 函数创建原生的系统线程,然后调用 函数将线程状态改为 状态,确保线程运行结束后资源的释放,最后返回线程id。

0x01 线程的执行

传给了 函数用来创建线程的func参数是 函数,arg参数包装了线程信息的boot对象,也就是说主线程创建的子线程将会执行 。

定义在 :

函数先对线程状态对象设置子线程的id,并初始化线程状态对象。

调用 获取GIL,定义在 :

实际上就是调用了 来获取GIL。

获取GIL之后对进程状态对象( )累加线程数,并调用 执行子线程的函数。

在子线程的全部计算完成之后,Python将销毁子线程。

0x02 线程的销毁

清除当前线程对应的线程状态对象,所谓清理,实际上比较简单,就是对线程状态对象中维护的东西进行引用计数的维护。

释放线程状态对象并释放GIL。

在 中,首先会删除清理后的当前线程状态对象,然后通过 释放GIL。

定义在 :

调用了 函数,定义在 :

函数获取mutex互斥锁之后将 置为0,释放GIL,并通知条件变量 。

清除和释放工作完成后,子线程就调用 退出了。

是一个平台相关的操作,完成各个平台上不同的销毁原生线程的工作。在POSIX Thread(pthread) 下,实际上就是调用 函数。

0x03 线程的调度

时间调度

当然,子线程是不会一直执行 到释放GIL,Python中持有GIL的线程会在某个时间释放GIL。

In the GIL-holding thread, the main loop (PyEvalEvalFrameEx) must beable to release the GIL on demand by another thread. A volatile booleanvariable (gildrop_request) is used for that purpose, which is checkedat every turn of the eval loop. That variable is set after a wait of microseconds on has timed out.

[Actually, another volatile boolean variable (eval_breaker) is usedwhich ORs several conditions into one. Volatile booleans aresufficient as inter-thread signalling means since Python is runon cache-coherent architectures only.]

A thread wanting to take the GIL will first let pass a given amount oftime ( microseconds) before setting gildroprequest. Thisencourages a defined switching period, but doesn't enforce it sinceopcodes can take an arbitrary time to execute.

The value is available for the user to read and modifyusing the Python API .

我们看 函数,定义在 :

是虚拟机执行字节码的主入口函数,当满足 时,当前线程会释放GIL,其他等待GIL的线程会尝试获取GIL并执行。

会在 等待超时被设置。而超时时间被设置在 :

会在 初始化的时候会被设置。

默认是0.005s,可以通过sys.getswitchinterval来查看 时间间隔:

阻塞调度

Python3中除了时间调度之外,还有一种阻塞调度:当线程执行I/O操作,或者sleep时,线程将会挂起,释放GIL。

以 为例,实现在 :

函数是跨平台实现,上面只保留非windows平台的代码。

Python在这里使用select来实现sleep阻塞,可以看到select前后有两个宏定义:

定义在 :

和 宏分别调用了 和 函数:

定义在 :

:设置当前线程状态对象为NULL,释放GIL,并保存到 变量。

:获取GIL,重新设置当前线程状态对象。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180707G161MS00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券