Python‘最難’的問題——GIL問題

[TOC]python

1、什麼是GIL

GIL(解釋器全局鎖)

從名字上看能告訴咱們不少東西,很顯然,這是一個加在解釋器上的全局(從解釋器的角度看)鎖(從互斥或者相似角度看)。編程

首先來看回顧一下什麼是鎖:安全

爲何加鎖

因爲多線程共享進程的資源和地址空間,所以,在對這些公共資源進行操做時,爲了防止這些公共資源出現異常的結果,必須考慮線程的同步和互斥問題。多線程

加鎖的做用

一、用於非線程安全,二、控制一段代碼,確保其不產生調度混亂。spa

GIL官方給出的解釋

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython’s memory management is not thread-safe. (However, since the GIL exists, other features have grown to depend on the guarantees that it enforces.)線程

在CPython中,全局解釋器鎖(global interpreter lock, GIL)是一個互斥體,它防止多個本機線程同時執行Python字節碼。這個鎖是必要的,主要是由於CPython的內存管理不是線程安全的。(然而,自從GIL存在以來,其餘特性已經逐漸依賴於它強制執行的保證。)設計

2、GIL的影響

GIL的設計缺陷

從上文的介紹和官方的定義來看,GIL就是一把全局排他鎖。這種方式固然很安全,可是這對於任何Python程序來講,無論有多少的處理器,任什麼時候候都老是隻有一個線程在執行。毫無疑問全局鎖的存在會對多線程的效率有不小影響。code

可是咱們課上講的例子,並非這樣啊接口

上課的多線程例子:進程

from threading import Thread
import time

def task():
    time.sleep(5)
    
def run():
    t1 = Thread(target=task)
    t2 = Thread(target=task)
    start = time.time()
    t1.start()
    t1.join()

    t2.start()
    t2.join()
    end = time.time()
    print(f'Total time: {end - start}')
	
    '''
    串行結果:
    Total time:10
    '''

    
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    
    '''
    並行結果:
    Total time:5
    '''

可是!

看看這個

from threading import Thread
import time


def counter():
    # 計數到一億
    i = 0
    for _ in range(100000000):
        i += 1
    return True
  


def run():
    t1 = Thread(target=counter)
    t2 = Thread(target=counter)
    start = time.time()
    t1.start()
    t1.join()
    t2.start()
    t2.join()
    end = time.time()
    print(f'Total time: {end - start}')
	
    '''
	串行結果(即單線程):
	Total time: 15.838918209075928
	'''
	
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    
    '''
    並行結果:
    Total time: 16.79609990119934 
    (其實他們兩個結果我跑的時候不相上下,可是也能說明問題)
    '''
if __name__ == '__main__':
    run()

問題來了。

爲何多線程並行比單線程慢,可是老師講的例子爲何多線程並行時間又更少?

剛剛也說了是由於GIL致使的,python解釋器任什麼時候候都是一個線程在執行。

課上例子多線程並行快的緣由是: 線程作的是i/o操做, 能夠掛起當前線程去執行下一線程。由於遇到像 i/o操做這種 會有時間空閒狀況 形成cpu閒置的狀況會釋放GIL

因此在python上只要在進行耗時的IO操做的時候,能釋放GIL,這樣也仍是能夠提高運行效率的。

爲何GIL有這個缺陷而不改進?

改不了!

爲何改不了

歷史遺留問題:由於硬件的升級cpu單核變多核,當時python開發者爲了利用多核,就出現了多線程編程,而隨之帶來的就是線程間數據一致性和狀態同步的困難。 python開發者的解決方法就是加GIL這把大鎖。

可是人們以後發現了這種多線程編程實現方式是垃圾的,想去除他,可是這時候已經發現離不開他了,大量的開發者已經重度依賴GIL了。

有了GIL的存在,python有這兩個特色

    一、進程能夠利用多核,可是開銷大。

    二、多線程開銷小,卻沒法利用多核優點。

  也就是說Python中的多線程是假的多線程,Python解釋器雖然能夠開啓多個線程,但同一時間只有一個線程能在解釋器中執行,而作到這一點正是因爲GIL鎖的存在,它的存在使得CPU的資源同一時間只會給一個線程使用,而因爲開啓線程的開銷小,因此多線程纔能有一片用武之地,否則就真的是雞肋了。

  而python的多線程到底有沒有用呢?

​ 有用。咱們須要看任務是I/O密集型,仍是計算密集型:

    若是是I/O密集型任務,有再多核也沒用,即能開再多進程也沒用,因此咱們利用python的多線程一點問題也沒有;

    若是是計算密集型任務,咱們就直接使用多進程就能夠了

如何解決GIL鎖帶來的問題

一、不用cpython,使用jpython(不太可能)

二、使用多進程完成多線程任務(python專家推薦)

用multiprocess來代替Thread

multiprocess庫的出現很大程度上是爲了彌補thread庫由於GIL而低效的缺陷。它完整的複製了一套thread所提供的接口方便遷移。惟一的不一樣就是它使用了多進程而不是多線程。每一個進程有本身的獨立的GIL,所以也不會出現進程之間的GIL爭搶。

可是它的引入會增長程序實現時線程間數據通信和同步的困難。

三、使用多線程時用c語言實現 (還要學c,我可去他的)

相關文章
相關標籤/搜索