python3线程gil_深入学习python多线程与GIL

python 多线程效率

在一台8核的CentOS上,用python 2.7.6程序执行一段CPU密集型的程序。

import time

def fun(n):#CPU密集型的程序

while(n>0):

n -= 1

start_time = time.time()

fun(10000000)

print('{} s'.format(time.time() - start_time))#测量程序执行时间

测量三次程序的执行时间,平均时间为0.968370994秒。这就是一个线程执行一次fun(10000000)所需要的时间。

下面用两个线程并行来跑这段CPU密集型的程序。

import time

import threading

def fun(n):

while(n>0):

n -= 1

start_time = time.time()

t1 = threading.Thread( target=fun, args=(10000000,) )

t1.start()

t2 = threading.Thread( target=fun, args=(10000000,) )

t2.start()

t1.join()

t2.join()

print('{} s'.format(time.time() - start_time))

测量三次程序的执行时间,平均时间为2.150056044秒。

为什么在8核的机器上,多线程执行时间并不比顺序执行快呢?

再做另一个实验,用下面的命令,把8核cpu中的7个核禁掉。

[xxx]# echo 0 > /sys/devices/system/cpu/cpu1/online

[xxx]# echo 0 > /sys/devices/system/cpu/cpu2/online

[xxx]# echo 0 > /sys/devices/system/cpu/cpu3/online

[xxx]# echo 0 > /sys/devices/system/cpu/cpu4/online

[xxx]# echo 0 > /sys/devices/system/cpu/cpu5/online

[xxx]# echo 0 > /sys/devices/system/cpu/cpu6/online

[xxx]# echo 0 > /sys/devices/system/cpu/cpu7/online

然后在运行这个多线程的程序,三次平均时间为2.533491453秒。为什么多线程程序在多核上跑的时间只比单核快一点点呢?

这就要提到python程序多线程的实现机制了。

Python多线程实现机制

python的多线程机制,就是用C实现的真实系统中的线程。线程完全被操作系统控制。

python内部创建一个线程的步骤是这样的:

创建一个数据结构PyThreadState,其中含有一些解释器状态

调用pthread创建线程

执行线程函数

由于python是解释形动态语言,所以在实现线程时,需要PyThreadState结构来保存一些信息:

当前的stack frame (对python代码)

当前的递归深度

线程ID

可选的tracing/profiling/debugging hooks

PyThreadState是C语言实现的一个结构体(摘自[2]):

typedef struct _ts {

struct _ts *next; # 链表指正

PyInterpreterState *interp; # 解释器状态

struct _frame *frame; # 当前的stack frame

int recursion_depth; # 当前的递归深度

int tracing;

int use_tracing;

Py_tracefunc c_profilefunc;

Py_tracefunc c_tracefunc;

PyObject *c_profileobj;

PyObject *c_traceobj;

PyObject *curexc_type;

PyObject *curexc_value;

PyObject *curexc_traceback;

PyObject *exc_type;

PyObject *exc_value;

PyObject *exc_traceback;

PyObject *dict;

int tick_counter;

int gilstate_counter;

PyObject *async_exc;

long thread_id; # 线程ID

} PyThreadState;

从目前最新的python源码中来看,这个结构体中的内容已经有所改变,但记录解释器状态的指针PyInterpreterState *interp依然存在。

python解释器实现时,用了一个全局变量(_PyThreadState_Current)

PyThreadState *_PyThreadState_Current = NULL;

_PyThreadState_Current指向当前执行线程的PyThreadState数据结构。解释器通过这个变量,来获取当前所执行线程的信息。

python程序中,有一个全局解释器锁GIL来控制线程的执行,每一个时刻只允许一个线程执行。

GIL的行为

GIL最基本的行为只有下面两个:

当前执行的线程持有GIL

线程遇到I/O阻塞时,会释放GIL。(阻塞等待时,就释放GIL,给另一个线程执行的机会)

那么,如果遇到CPU密集型的线程,一直占用CPU,不会被I/O阻塞,是不是其它线程就没有机会执行了呢?

非也,为了避免这种情况,解释器还会周期性的check并执行线程调度。

解释器周期性check行为,做的就是下面这3件事:

复位tick计数器

在主线程中,检查有没有需要处理的信号

让当前执行线程释放(Release)GIL,让其他线程获取(acquire)GIL并执行(给其他线程执行的机会)

而解释器check的周期,默认是100个tick。解释器的tick并不是基于时间的,每个tick大致相当于一条汇编指令的执行时间。

从解释器的check行为中可以看到,只有主线程中会处理信号,子线程中都不处理信号。所以python多线程程序,会给人一种无法处理Ctrl+C的假象,因为大部分情况下主线程被block住了,无法处理SIGINT信号。

注意python中并没有实现线程调度,python的多线程调度完全依赖于操作系统。所以python多线程编程中没有线程优先级等概念。

GIL的实现

python的GIL并不是简单的用lock实现的,GIL是用signal实现的。

线程获取(acquire)GIL前,先检查有没有被free,如果没有,就sleep等待signal

线程释放GIL时,还要发送signal

参考

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值