1. 程式人生 > >python多執行緒為什麼不能利用多核cpu

python多執行緒為什麼不能利用多核cpu

利用 ctypes 繞過 GIL

ctypes 與 Python 擴充套件不同,它可以讓 Python 直接呼叫任意的 C 動態庫的匯出函式。你所要做的只是用 ctypes 寫些 python 程式碼即可。最酷的是,ctypes 會在呼叫 C 函式前釋放 GIL。所以,我們可以通過 ctypes 和 C 動態庫來讓 python 充分利用物理核心的計算能力。讓我們來實際驗證一下,這次我們用 C 寫一個死迴圈函式

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

用上面的 C 程式碼編譯生成動態庫 libdead_loop.so

 (Windows 上是 dead_loop.dll

,接著就要利用 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 寫自動化測試用例,通過 ctypes 直接呼叫 C 模組的介面來對這個模組進行黑盒測試,哪怕是有關該模組 C 介面的多執行緒安全方面的測試,ctypes 也一樣能做到。