Python程式執行緒協程GIL閉包與高階函式(五)

善良小郎君發表於2018-06-20

1 GIL執行緒全域性鎖

​ 執行緒全域性鎖(Global Interpreter Lock),即Python為了保證執行緒安全而採取的獨立執行緒執行的限制,說白了就是一個核只能在同一時間執行一個執行緒.對於io密集型任務,python的多執行緒起到作用,但對於cpu密集型任務,python的多執行緒幾乎佔不到任何優勢,還有可能因為爭奪資源而變慢。

在分析執行緒全域性鎖之前我們先聊下python.

(1) python語言的癥結

​ python是解釋型語言 , 不像c++這樣是編譯型語言 : 程式輸入到編譯器,編譯器再根據語言的語法進行解析 . 編譯型語言之所以可以進行深層次的底層優化是因為可以直接看到程式碼的整體部分,這使得它可以對不同的語言指令之間的互動進行推理,從而給出更有效的優化手段。

​ Python是解釋型語言,程式被輸入到直譯器執行,直譯器只會按照python的規則以及在執行過程中怎樣去動態的應用這些規則,由於直譯器沒法很好的對程式進行推導. 而Python大部分的優化也是直譯器自身的優化, 換句話說,直譯器優化後,Python不用修改原始碼就能享受優化的好處. 因此關鍵問題來了,如果假定其他條件不變, python程式的執行速度跟直譯器速度相關 . 不管你怎樣優化自己的程式,你的程式的執行速度還是依賴於直譯器執行你的程式的效率。這就很明顯的解釋了為什麼我們需要對優化Python直譯器做這麼多的工作了。

(2) Python的多執行緒

​ 要想利用多核系統,Python必須支援多執行緒執行. 目前來說,多執行緒執行還是利用多核系統最常用的方式 .,但是多個執行緒競爭一個共享資源資料將會是意見很糟糕的事, 因此直譯器要留意的是避免在不同的執行緒操作內部共享的資料。同時它還要保證在管理使用者執行緒時保證總是有最大化的計算資源。那麼資料的安全機制怎麼操作呢? 答案就是直譯器全域性鎖 , 這是一個加在直譯器上的全域性鎖。這種方式當然很安全,但是它隱含意思:對於任何Python程式,不管有多少的處理器,任何時候都總是隻有一個執行緒在執行。

這就是為什麼總會碰到 ”全新的多執行緒Python程式執行得比其只有一個執行緒的時候還要慢? ”

(3) 如何解決GIL

​ 很多有經驗的開發人員都會回答:”不要使用多執行緒,使用多程式!!”,但是這並沒有解決多執行緒的核心問題, 國外有做過這方面的嘗試,通過移除GIL,且用細粒度的鎖來代替,但是帶來的代價是單執行緒執行程式執行速度下降很大.在新的GIL中, 用一個固定的超時時間來指示當前的執行緒以放棄這個鎖。在當前執行緒保持這個鎖,且當第二個執行緒請求這個鎖的時候,當前執行緒就會在5ms後被強制釋放掉這個鎖(這就是說,當前執行緒每5ms就要檢查其是否需要釋放這個鎖)。當任務是可行的時候,這會使得執行緒間的切換更加可預測。 但是這也不是一個完美的方案, 因此GIL執行緒全域性鎖仍然是Python最大的挑戰,希望有大牛能完美解決!!!!

2 執行緒 程式與協程

多執行緒

執行緒是屬於程式的,執行緒執行在程式空間內,同一程式所產生的執行緒共享同一記憶體空間,當程式退出時該程式所產生的執行緒都會被強制退出並清除 .

多執行緒就是允許一個程式記憶體在多個控制權,以便讓多個函式同時處於啟用狀態,從而讓多個函式的操作同時執行。即使是單CPU的計算機,也可以通過不停地在不同執行緒的指令間切換,從而造成多執行緒同時執行的效果。

多執行緒相當於一個併發(concunrrency)系統。併發系統一般同時執行多個任務。如果多個任務可以共享資源,特別是同時寫入某個變數的時候,就需要解決同步的問題.

在併發情況下,指令執行的先後順序由核心決定。同一個執行緒內部,指令按照先後順序執行,但不同執行緒之間的指令很難說清除哪一個會先執行。因此要考慮多執行緒同步的問題。同步(synchronization)是指在一定的時間內只允許某一個執行緒訪問某個資源。

協程

與執行緒的搶佔式排程不同,它是協作式排程。協程也是單執行緒,但是它能讓原來要使用非同步+回撥方式寫的非人類程式碼,可以用看似同步的方式寫出來。

1 協程在python中可以由生成器(generator )來實現 即可以通過yield控制程式的執行

2 Stackless Python

3 greenlet模組

4 eventlet模組

多程式

multiprocessing包是Python中的多程式管理包。與threading.Thread類似,它可以利用multiprocessing.Process物件來建立一個程式。

 1 程式池 (Process Pool)可以建立多個程式。

 2 apply_async(func,args) 從程式池中取出一個程式執行func,args為func的引數。它將返回一個AsyncResult的物件,你可以對該物件呼叫get()方法以獲得結果。

 3 close() 程式池不再建立新的程式

 4 join() wait程式池中的全部程式。必須對Pool先呼叫close()方法才能join。

3 閉包

​ 在一個外函式中定義了一個內函式,內函式裡運用了外函式的臨時變數,並且外函式的返回值是內函式的引用。這樣就構成了一個閉包。

​ 一般情況下,在我們認知當中,如果一個函式結束,函式的內部所有東西都會釋放掉,還給記憶體,區域性變數都會消失。但是閉包是一種特殊情況,如果外函式在結束的時候發現有自己的臨時變數將來會在內部函式中用到,就把這個臨時變數繫結給了內部函式,然後自己再結束。

​ 當一個內嵌函式引用其外部作作用域的變數,我們就會得到一個閉包. 重點是函式執行後並不會被撤銷,只是遷移到內函式上 ,總結一下,建立一個閉包必須滿足以下幾點:

  1. 必須有一個內嵌函式
  2. 內嵌函式必須引用外部函式中的變數
  3. 外部函式的返回值必須是內嵌函式
#閉包函式的例項
# outer是外部函式 a和b都是外函式的臨時變數
def outer( a ):
    b = 10
    # inner是內函式
    def inner():
        #在內函式中 用到了外函式的臨時變數
        print(a+b)
    # 外函式的返回值是內函式的引用
    return inner

if __name__ == `__main__`:
    # 在這裡我們呼叫外函式傳入引數5
    #此時外函式兩個臨時變數 a是5 b是10 ,並建立了內函式,然後把內函式的引用返回存給了demo
    demo = outer(5)
    # demo存了外函式的返回值,也就是inner函式的引用,這裡相當於執行inner函式
    demo() # 15

    demo2 = outer(7)
    demo2()#17

​ 外函式結束的時候發現內部函式將會用到自己的臨時變數,這兩個臨時變數就不會釋放,會繫結給這個內部函式,因此外函式的結束並沒有清空a,b的數值,而是繫結給了內函式。

4 高階函式,函數語言程式設計

(1)lambda函式

lambda函式也成為匿名函式是函數語言程式設計的一種,通常是在需要一個函式,但是又不想費神去命名一個函式的場合下使用,同類的還有filter ,reduce等等

#將list每個元素進行平方
#map的語法是 : map(f,a)  也就是將函式 f 依次套用在 a 的每一個元素上面
map( lambda x: x*x, [y for y in range(10)] )

#類似於下面的寫法
def sq(x):
    return x * x
map(sq, [y for y in range(10)])
#多定義了一個函式,尤其在只是用一次的情況下

所以你會發現自己如果能將「遍歷列表,給遇到的每個元素都做某種運算」的過程從一個迴圈裡抽象出來成為一個高階函式 map,然後用 lambda 表示式將這種運算作為引數傳給 map 的話,思維水平就要高出一般的原寫法。

(2)reduce函式

reduce()函式也是Python內建的一個高階函式。

reduce()函式接收的引數和 map()類似,一個函式 f,一個list,但行為和 map()不同,reduce()傳入的函式 f 必須接收兩個引數,reduce()對list的每個元素反覆呼叫函式f,並返回最終結果值。

def f(x, y):
    return x + y
reduce(f, [1, 3, 5, 7, 9])

#先計算頭兩個元素:f(1, 3),結果為4;
#再把結果和第3個元素計算:f(4, 5),結果為9;
#再把結果和第4個元素計算:f(9, 7),結果為16;
#再把結果和第5個元素計算:f(16, 9),結果為25;
#由於沒有更多的元素了,計算結束,返回結果25

(3) filter函式

filter()函式是 Python 內建的另一個有用的高階函式,filter()函式接收一個函式 f 和一個list,這個函式 f 的作用是對每個元素進行判斷,返回 True或 False,filter()根據判斷結果自動過濾掉不符合條件的元素,返回由符合條件元素組成的新list。

#list [1, 4, 6, 7, 9, 12, 17]中刪除偶數,保留奇數
def is_odd(x):
    return x % 2 == 1

filter(is_odd, [1, 4, 6, 7, 9, 12, 17])
#輸出
[1, 7, 9, 17]

當然還可以實現很多功能,取決與函式定義的功能(刪除 None 或者空字串 等),filter僅僅是輔助函式起到過濾作用。

(4)函數語言程式設計

def func(x):
    def funcx(y):
        return x+y
    return funcx

func2 = func(2)
func5 = func(5)

print(func2(5)) # 輸出 7
print(func5(5)) # 輸出 10

inc()函式返回了另一個函式incx(),於是我們可以用inc()函式來構造各種版本的inc函式,比如:inc2()和inc5()。這個技術其實就是上面所說的Currying技術。從這個技術上,你可能體會到函數語言程式設計的理念:把函式當成變數來用,關注於描述問題而不是怎麼實現,這樣可以讓程式碼更易讀。


相關文章