【转】python 线程,GIL 和 ctypes

转自:http://zhuoqiang.me/a/python-thread-gil-and-ctypes

4 利用 ctypes 绕过 GIL

      ctypes 与 python 扩展不同,它可以让 Python 直接调用任意的 C 动态库的导出函数。你所要做的只是用 ctypes 这个 Python 库写些 python 代码即可。最酷的是,ctypes 会在调用 C 的函数前释放 GIL。所以,我们可以通过 ctypes 和 C 动态库来让 python 充分利用物理内核的计算能力。让我们来实际验证一下,我们用 C 写一个死循环函数

extern"C"
{
  void DeadLoop()
  {
    while (true);
  }
}

     用上面的 C 代码编译生成动态库 libdead_loop.so,接着就要利用 ctypes 来在 python 里 load 这个动态库,分别在主线程和新建线程里调用其中的 DeadLoop

from ctypes import *
from threading import Thread
 
lib = cdll.LoadLibrary("libdead_loop.so")
t = Thread(target=lib.DeadLoop)
t.start()
 
lib.DeadLoop()

      这回再看看 system monitor,Python 解释器进程有两个线程在跑,而且双核 CPU 全被占满了!ctypes 确实很给力。需要提醒的是,GIL 是被 ctypes 在调用 C 的函数前释放了。但是 Python 解释器还是会在执行任意一段 Python 代码时锁 GIL 的。如果你使用 Python 的代码做为 C 函数的 callback,那么只要 Python 的 callback 方法被执行时,GIL 还是会跳出来的。比如下面的例子:

extern"C"
{
  typedef void Callback();
  void Call(Callback* callback)
  {
    callback();
  }
}
from ctypes import *
from threading import Thread
 
def dead_loop():
    while True:
        pass
 
lib = cdll.LoadLibrary("libcall.so")
Callback = CFUNCTYPE(None)
callback = Callback(dead_loop)
 
t = Thread(target=lib.Call, args=(callback,))
t.start()
 
lib.Call(callback)

      注意这里与上个例子的不同之处,这次的死循环是发生在 Python 代码里 (DeadLoop 函数) 而 C 代码只是负责去调用这个 callback 而已。运行这个例子,你会发现 CPU 占用率还是只有 50% 不到。GIL 又起作用了。

      其实,从上面的例子,我们还能看出 ctypes 的一个应用,那就是用 python 写测试 case,通过 ctypes 直接调用 C 模块的接口来对这个模块进行黑盒测试,哪怕是有关该模块 C 接口的多线程安全方面的测试,ctypes 也一样能做到。

posted on 2012-09-05 22:34  J.evan  阅读(233)  评论(0)    收藏  举报