Python屏蔽GIL鎖實踐(調研+轉載+整理)

環境:

Ubuntu19.10

四核八線程.

題外話,八線程屬於超線程概念,程序員不可控制,屬於操作系統調度的工作.

基本概念:

什麼時候使用併發/並行?

資料中有兩種說法:

一種是:

涉及到多核就是並行,單核IO異步就是併發

另一種是:

整個系統服務於IO異步任務就是併發,涉及到CPU密集就是並行

本文以第一種爲準

--------------------------------

消除GIL鎖爲什麼會導致單線程變慢?

參考[6]的回答

Python多線程相當於單核多線程[7]

--------------------------------

網上資料的多線程包含:

單核多線程和多核多線程.

單核多線程就IO異步.

多核多線程大多數情況下指的是多核情況.

根據[4]

I'd welcome a set of patches into Py3k only if the performance for a single-threaded program (and for a multi-threaded but I/O-bound program) does not decrease”(這個意思是如果移除GIL鎖,那麼單線程性能會下降,也就是I/O異步性能會下降)

 

Each Python process gets its own Python interpreter and memory space so the GIL won't be a problem. (每個進程都有一個GIL鎖)

網上說的"Python的多線程是雞肋"的意思指的是多核多線程

有了多線程執行異步爲啥還要多協程執行IO異步?

根據[5]:

好處是協程的開銷成本更低.

 

GIL鎖影響的是IO密集還是CPU密集任務?

The GIL does not have much impact on the performance of I/O-bound multi-threaded programs as the lock is shared between threads while they are waiting for I/O.[4](影響的是CPU密集任務而不是IO密集任務)

 

GIL鎖在你調用開源庫的時候,依然存在嗎?

取決於底層是不是C寫的,如果是C寫的,屏蔽了GIL鎖,那就不存在,否則就存在

----------------------------------------------------------------------------------------------------------------------

網上關於屏蔽GIL鎖的資料如下:

[1]屏蔽了GIL鎖,沒有完整代碼

[3]講了原理,但是沒有實操

本篇博客重點解析[2].

GIL鎖每個進程都有一個,鎖的到底是什麼?

鎖的是多線程進行CPU密集計算時候的場景.案例如下[4]:

單線程:

# single_threaded.py
import time

COUNT = 50000000

def countdown(n):
    while n>0:
        n -= 1

start = time.time()
countdown(COUNT)
end = time.time()

print('Time taken in seconds -', end - start)

Time taken in seconds - 2.2484254837036133


多線程:

# multi_threaded.py
import time
from threading import Thread

COUNT = 50000000

def countdown(n):
    while n>0:
        n -= 1

t1 = Thread(target=countdown, args=(COUNT//2,))
t2 = Thread(target=countdown, args=(COUNT//2,))

start = time.time()
t1.start()
t2.start()
t1.join()
t2.join()
end = time.time()

print('Time taken in seconds -', end - start)

Time taken in seconds - 3.074549674987793

 

多進程:

from multiprocessing import Pool
import time

COUNT = 50000000
def countdown(n):
    while n>0:
        n -= 1

if __name__ == '__main__':
    pool = Pool(processes=2)
    start = time.time()
    r1 = pool.apply_async(countdown, [COUNT//2])
    r2 = pool.apply_async(countdown, [COUNT//2])
    pool.close()
    pool.join()
    end = time.time()
    print('Time taken in seconds -', end - start)

結論:

CPU密集任務速度上,

多核多進程>多核多線程>單線程

----------------------------------------------下面是屏蔽GIL鎖實踐------------------------------------------------------------------------------

回到前面一個話題,python的多線程真的是雞肋嗎?

並不是,請看下面實驗,下面實驗受到[2]的啓發,但是因爲[2]的代碼不完整,所以我自己想出了一個demo

我們把count提升100倍,下面是三個代碼文件:

pycall.c

#include <stdio.h>  
#include <stdlib.h>  
// #include <iostream.h>  
int foo(int a, int b)  
{  
	long long int count=5000000000;
	   while( count > 0 )
   {
       // cout << "a 的值:" << a << endl;
       count--;
       if(count==2500000000)
            printf("計數中%lld\n", count);
       if(count==0)
            printf("計數結束%lld\n", count);
   };
  printf("you input %d and %d\n", a, b);  
  return 1;  
}

因爲我們等下要進行多線程測試,

所以我們在上面代碼中故意加入一些if語句用來監控計數過程,如果多線程生效 那麼就會打印兩次,

如果GIL鎖依然存在,那麼整個運行過程會比單線程還要慢一倍左右

 

pycall-1thread.py

import ctypes  
import threading
import time

if __name__ == '__main__':
    start=time.time()
    my_lib=ctypes.cdll.LoadLibrary("./libpycall.so")  
    t1=threading.Thread(target=my_lib.foo,args=(1,1))
    t1.start()
    t1.join()
    end=time.time()
    print('耗時=',end-start)

 

pycall-2thread.py

import ctypes  
import threading
import time

if __name__ == '__main__':
    start=time.time()
    my_lib=ctypes.cdll.LoadLibrary("./libpycall.so")  
    t1=threading.Thread(target=my_lib.foo,args=(1,1))
    t2=threading.Thread(target=my_lib.foo,args=(2,2))
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    end=time.time()
    print('耗時=',end-start)

 

運行方法:

先生成.so文件

gcc -o libpycall.so -shared -fPIC pycall.c

然後:

命令 運行結果
python pycall-1thread.py 計數中2500000000
計數結束0
you input 1 and 1
耗時= 10.828569173812866
python pycall-2thread.py 計數中2500000000
計數中2500000000
計數結束0
you input 1 and 1
計數結束0
you input 2 and 2
耗時= 10.749252796173096

可以看到,雙線程運行與單線程運行速度一致,真正發揮了多核的威力.

所以明白如何使用python的多線程嗎?

在關鍵部位使用C/C++代碼來撰寫,屏蔽GIL鎖,大大提升計算速度.

C/C++寫起來如果覺得繁瑣,可以參考opencv,很多可以直接拿來用喔


[1]Python解決GIL鎖的辦法
[2]python的GIL鎖優化方案及性能對比
[3]GIL鎖

[4]What is the Python Global Interpreter Lock (GIL)?

[5]線程和進程的區別是什麼? - 無與童比的回答 - 知乎

[6]python中去掉gil會大幅降低單線程的執行速度,怎麼理解?

[7]爲什麼有人說 Python 的多線程是雞肋呢? - DarrenChan陳馳的回答 - 知乎

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章