天天看點

Python 程序線程協程 GIL 閉包 與高階函數(五)

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技術。從這個技術上,你可能體會到函數式程式設計的理念:把函數當成變量來用,關注于描述問題而不是怎麼實作,這樣可以讓代碼更易讀。