当前位置: 技术文章>> 一篇文章详细讲解Python线程安全性的实现

文章标题:一篇文章详细讲解Python线程安全性的实现
  • 文章分类: 后端
  • 13585 阅读

Python线程安全性指的是在Python环境中,多个线程可以并发或并行地执行Python开发者分配的任务,并且任务执行完毕后的结果总是正确的。在多线程执行Python任务时,但凡有一次任务没有得到预期的结果,就不能说明我们所设计的Python多线程程序是安全的。

CPython官方在实现Python线程安全时,将GIL进行了封装,并且将其实现过程封装到Python的内核实现层面,具体的实现源代码文件名称为pycore_gil.h,即CPython官方将GIL的实现过程封装成C语言的头文件。pycore_gil.h文件中的源代码如下。

首先来看pycore_gil.h头文件中的定义,Py_INTERNAL_GIL_H是GIL的内核实现定义。在本文件中通过取反的方式进行引入,即先判断Py_INTERNAL_GIL_H没有引入该文件,如果这一判断结果为真,则引入Py_INTERNAL_GIL_H;如果这一判断结果为假,则不再重复引入Py_INTERNAL_GIL_H。__cplusplus是在C语言文件中定义C++拓展程序的语法形式,由以下代码段组成:

pycore_gil.h头文件中还有几个地方需要注意。首先来看Py_BUILD_CORE,Py_BUILD_CORE是Python解释器识别Python内核文件的标识。如果Python内核文件中没有该标识,编译Python程序时就会提示“this header requires Py_BUILD_CORE define”错误,即只有文件中添加了Py_BUILD_CORE标识,才能被Python解释器识别为内核文件,并在内核编译期进行编译。pycore_atomic.h是Python实现原子性的内核头文件。

该文件可以实现Python程序全局的原子性,也可以实现具体Python操作的原子性。FORCE_SWITCHING表示线程及线程上下文的切换方式。该切换方式有两种:一种是操作系统CPU内核自动管理和切换线程及线程上下文,该方式也是CPython官方实现线程及线程上下文切换的默认方式;另一种是手动强制切换,我们可以在运行Python程序时,通过向Python解释器配置FORCE_SWITCHING参数来改变Python线程及线程上下文的切换方式为手动强制切换。

接着来看名为_gil_runtime_state的结构体定义,它是GIL的核心实现和调用的封装。_gil_runtime_state结构体分别由无符号long类型的变量interval、_Py_atomic_address类型的变量last_holder、_Py_atomic_int类型的变量locked、无符号long类型的变量switch_number、PyCOND_T类型的变量cond、PyMUTEX_T类型的变量mutex,以及对FORCE_SWITCHING状态的判断组成。该结构体规定了GIL的运行流程和管理措施。

变量interval表示GIL的执行时间,单位为μs,且不能是负数。在GIL被触发时,变量interval随即开始工作,直到GIL执行完毕。这个时间间隔就是GIL的执行时间,开发者可以通过该变量获取GIL执行程序所消耗的时间。变量last_holder表示最后持有GIL的标记地址,开发者可以通过该变量来查看最后持有GIL的Python程序。

变量locked表示Python程序是否被GIL修饰,如果没有被修饰,说明Python程序没有获取到GIL, locked变量值为-1,否则不为-1,而是一个大于0的值。同样,该变量是原子性变量,开发者可以通过该变量的值来判断Python程序有没有获取GIL。

变量switch_number表示GIL的切换次数,即锁被获取和锁被释放之间的切换次数。该变量不能是负数,只能是大于0的正整数。开发者可以通过该变量的值来查看Python程序中GIL的切换次数,从而判断Python线程是否正常运转。变量cond表示条件的意思,可以理解为影响线程安全性的条件。该变量支持设置允许一个或多个线程等待,直到获取到GIL的线程释放锁了之后,逐个解除等待。

开发者可以通过查看该变量是否有数据来判断线程是否处于等待状态,如果有数据,表明线程仍然处于等待状态。变量mutex表示Python中互斥锁的实现标记位。Python通过引入该变量来保证在同一时刻只能允许一个线程获取GIL,并且对获取GIL的线程进行互斥保护。

最后来看_gil_runtime_state结构体的最后部分,代码如下所示。

上述代码段表示如果开发者启用了线程和线程上下文手动强制切换方式,那么GIL在运行时就会同步开启手动Python线程的切换和手动Python互斥锁的切换,Python解释器不会再自动管理Python线程的切换和Python互斥锁的切换;如果开发者没有启用手动强制切换方式,GIL在运行时并不会将手动Python线程的切换和手动Python互斥锁的切换进行应用。

通过上述_gil_runtime_state结构体的定义,我们实现了GIL的整体管理。在编写Python程序时,开发者并不需要了解Python GIL的内部运转过程,只需要清楚在默认情况下Python线程被GIL保护着。

通过分析上述GIL实现的结构体_gil_runtime_state可知,Python在实现线程安全时,首先为每个访问临界区的Python线程统一添加GIL,然后通过变量interval和变量last_holder的相互配合,来确定每一个线程的执行时间和最大执行时间边界值,如果发现某个线程的执行时间太长,则会自动终止该线程的运行,并通过变量locked来改变多线程的状态。在多线程执行过程中,Python通过变量cond和变量mutex来规定每个线程的执行时机和线程状态切换时机,最后通过变量switch_number在后台静默记录每个线程对应的切换次数,以备不时之需。


推荐文章