Python GIL

CPython GIL 的全称是 global interpreter lock。看它的名字就知道,只有获得了这个锁,才有在python解释器上执行python代码的权利。由于历史的原因这个锁的粒度太大了,我们这里就讨论下这个锁下面,对python性能有着怎样的影响。

 

  •  先来做个简单的测试实验

 测试代码如下,简单的调用 count 函数进行100万次减法运算。test 函数采用单线程分别执行2次 count 函数;而test2 函数会启动两个子线程,各执行一次 count 函数。

 1 from threading import Thread
 2 from timeit import Timer
 3 
 4 def count(n):
 5     while n > 0:
 6         n -= 1
 7 
 8 def test():
 9     count(1000000)
10     count(1000000)
11 
12 def test2():
13     t1 = Thread(target=count, args=(1000000,))
14     t2 = Thread(target=count, args=(1000000,))
15 
16     t1.start()
17     t2.start()
18 
19     t1.join()
20     t2.join()
21 
22 
23 
24 t = Timer('test()''from __main__ import test')
25 t2 = Timer('test2()''from __main__ import test2')
26 print t.timeit(10)
27 print t2.timeit(10)

 

测试机器CPU为Intel M560双核超线程,系统为XP SP3,为了比较 GIL 分别在多核和单核CPU上的表现,我们会手动设置进程的Affinity,即让进程只运行在指定的某个CPU上来模拟单核时的情况。

 

 结果:

单位:秒
CPython 2.5 CPython 2.5单核 CPython 2.7 CPython 2.7单核 CPython 3.2 CPython 3.2单核 IronPython 2.7
test(单) 1.734822722 1.804248864 1.098758082 1.033799827 1.91157256 1.823544892 0.859375
test2(多) 3.296082603 1.606700572 2.588538208 0.970103463 2.422513476 1.743139142 0.546867371

 

 从图上我们可以明显的看出,CPython在多线程单核表现下,比单线程成绩略好;而在多线程多核下,就惨不忍睹了(CPython 2.5和2.7)慢1.5倍左右。这种情况一直持续到CPython 3.2才有很大的改观。

看了上面的结果,可能大家就有疑问了,GIL只是让多线程任务串行的去执行,理论上应该是和单线程的表现接近才对啊,为什么在多核多线程上表现如此大的差异?


 原因很简单,就是锁的竞争。

 

  • 我们先看看Python 3.2之前的GIL机制

伪代码如下:

 

while(true)
{
    execute_statement(); // 执行python代码

    if(--py_ticker < 0)
    {
        py_ticker = check_interval;

        sig(gil_cond); // 释放控制权
        wait(gil_cond, INFINITE); // 获得控制权
    }
}

每次线程释放控制权后,其立马进入等待状态。而在多核情况下,往往由于拥有者比在其它CPU运行的线程醒的更早——也就是自己释放后,立马自己又获得了。而其它CPU上运行的线程被唤醒时,发现锁还是被别人占着,空欢喜一场,无奈只能继续休眠等待。

 

  •  我们接着来看Python 3.2中新的GIL机制

新机制中,明确区划分了2个角色owner和waiter。owner根据waiter发来的请求,来释放控制权;采用了时间片,而不是老机制中100多个字节码;owner通知waiter后,它会等待waiter们中的一个真正获得控制权,然后自己才会从owner转变为waiter——举个简单的例子

新逻辑:

  总统任期到期后,它要等待新人当上总统后,他自己才能为下一届的竞选作准备——即不能连任;

而旧有的逻辑是:

  总统任期满后,马不停蹄的参加竞选,如果支持率高,该总统就能一直获得连任。

伪代码如下:

 Owner:

 

while(!should_switch)
{
    execute_statement(); // 执行python代码


sig(gil_cond);
wait(switch_cond, INFINITE);

         ↓


    Become Waiter

 

Waiter:

 

while(!wait(gil_cond, gil_interval))
{
    if(!switch_happend_at_last_interval())
        should_switch = true// 发送切换请求
}
sig(switch_cond); // 通知owner,我已经获得成功获得了控制权

         ↓

    Become Owner

 

 实现细节,可以参见python源码 Python-3.2.2/Python/ceval_gil.c 中的take_gil()、drop_gil()这两个函数。

 

参考文档:

 

www.dabeaz.com/python/NewGIL.pdf

 

 

 

 

 

posted on 2012-03-10 14:50  JesseFang  阅读(2618)  评论(0编辑  收藏  举报