Python的 併發、並行

weixin_34249678發表於2018-05-06

記錄:

併發:在一個時間段,處理多個任務,單核也可以併發(CPU分時間片);

並行:在同一個時刻,處理多個任務,必須多核才能並行;

一、Python實現併發的手段:

1、作業系統提供:程式、執行緒;

2、程式語言提供:協程:使用者空間的排程(py3);

題外話:

現在的作業系統,程式和執行緒的區別越來越小,因為程式越來越輕了;實際上,Linux的執行緒是通過程式實現的;

二、Python的程式和執行緒的區別:

Python每個程式都會啟動一個直譯器;

Python每個執行緒(一個程式下面的)共享一個直譯器;

ps:Python沒有提供主動停止執行緒的方法的;只能等執行緒處理完畢,或者主執行緒結束;所以線上程邏輯裡面一定要寫退出邏輯;

三、logging庫:

通常用logging代替print,因為logging是執行緒安全的,在多執行緒下,輸出表現正常,而print是非執行緒安全的,在多執行緒下,輸出表現會出現異常,例如,T1沒輸出完畢,T2又進行輸出了;

四、Python 執行緒的 daemon 屬性:

預設是false;

daemon:守護、守護程式;

守護程式:Daemon()程式是一直執行的服務端程式,又稱為守護程式。通常在系統後臺執行,沒有控制終端,不與前臺互動,Daemon程式一般作為系統服務使用。Daemon是長時間執行的程式,通常在系統啟動後就執行,在系統關閉時才結束。

知識點一:

當一個程式啟動之後,會預設產生一個主執行緒,因為執行緒是程式執行流的最小單元,當設定多執行緒時,主執行緒會建立多個子執行緒,在python中,預設情況下(其實就是setDaemon(False)),主執行緒執行完自己的任務以後,就退出了,此時子執行緒會繼續執行自己的任務,直到自己的任務結束;

知識點二:

當我們使用setDaemon(True)方法,設定子執行緒為守護執行緒時,主執行緒一旦執行結束,則全部執行緒全部被終止執行,可能出現的情況就是,子執行緒的任務還沒有完全執行結束,就被迫停止;

知識點三:

join所完成的工作就是執行緒同步,即主執行緒任務結束之後,進入阻塞(等待)狀態,一直等待其他的子執行緒執行結束之後,主執行緒在終止;

知識點四:

join有一個timeout引數:

當設定守護執行緒時,含義是主執行緒對於子執行緒等待timeout的時間將會殺死該子執行緒,最後退出程式。所以說,如果有10個子執行緒,全部的等待時間就是每個timeout的累加和。簡單的來說,就是給每個子執行緒一個timeout的時間,讓他去執行,時間一到,不管任務有沒有完成,直接殺死。

沒有設定守護執行緒時,主執行緒將會等待timeout的累加和這樣的一段時間,時間一到,主執行緒結束,但是並沒有殺死子執行緒,子執行緒依然可以繼續執行,直到子執行緒全部結束,程式退出。

t1.join(),子執行緒設定這個就是讓主執行緒阻塞(等待),那麼在哪裡設定了 t1.join(),主執行緒就在這裡等待t1執行完畢,再繼續往下執行;因為兩個執行緒順序完成,看起來象一個執行緒,所以稱為執行緒的合併,其實也是讓執行緒同步了;

注意(看到的結果是這樣,解釋可能不太正確):

(1)看圖:

11207398-44826dde970e151c.PNG

t1設定執行2秒;t2設定執行3秒;目的是為了t1先於t2執行完畢;

t1沒有設定daemon執行緒,所以當主執行緒結束後,t1仍然會繼續執行完畢,所以檢視結果:當輸出 main end ... 後,仍然有 t1 out ... 輸出;

t2設定為daemon執行緒,所以當主執行緒結束後,t2也被主執行緒殺死了(t1已結執行完畢),跟著結束了,程式(程式)也結束了,所以沒有 t2 out... 輸出;

注意,這裡一定要保證t1(非daemon子執行緒)先於t2執行完畢;因為如果t1要比t2耗時長的話,因為 t1是非daemon子執行緒,即是主執行緒結束了,t1仍然要繼續執行的,所以整個程式(程式)並不會結束,也就沒有殺死t2,導致t2也會執行完畢;看下面:

11207398-7b0f8425e0d991f6.PNG

可以看到:即是t2是daemon子執行緒,但是仍然執行完畢了,並沒有因為主執行緒的退出而被殺死,因為t1的執行時間比t2長;

ps:如果不是以繼承的方式建立執行緒,那麼run方法和start方法只能執行其中一個;通常也不建議使用繼承的方式;start方法是子執行緒執行的,啟動子執行緒;

要注意:主執行緒、父執行緒、子執行緒的關係;

五、Python中的ThreadLocal變數

注意,並不是子執行緒要執行的目標函式裡面的區域性變數,雖然每個執行緒即使是執行同一個函式,都會有自己的記憶體的,互不干擾,但是函式裡面的區域性變數,就是屬於函式的,到了另外一個函式不會認識此區域性變數;

而Python的threadlocal變數,是屬於執行緒的,此執行緒呼叫的所有函式都認識threadlocal變數,eg.:

global_data = threading.local()

def show():

        print (threading.current_thread().getName(), global_data.num

def thread_cal():

        global_data.num = 0

        for _ in xrange(1000):

            global_data.num += 1

        show()

每個執行緒都可以通過 global_data.num 獲得自己獨有的資料,並且每個執行緒讀取到的 global_data 都不同,真正做到執行緒之間的隔離。其他執行緒是不認識global_data.num的;

六、執行緒同步方式(前三個方式需要在具體瞭解)

執行緒的同步,有一層意思就是:執行緒之間的通訊,執行緒總是有某種關聯,才需要同步的,而同步就意味著阻塞(等待);

1、event

兩個執行緒之間事件的通知,我發現了一個事件,set,你就可以檢視。

2、lock (rlock?)

保護共享資源;

3、condition

生產者消費者模式;生產者喚醒消費者;


4、Barrier(柵欄)

就是執行緒們在等待,直到執行緒數滿足設定的數量之和,才繼續執行;

可以使用此方法開發併發測試工具;

一些關鍵語句:

barrier = threading.Barrier(3)  #等齊3個執行緒

worker_id = barrier.wait()  #執行緒執行到這一句,就等著,滿足數量之後,再往下走

barrier.n_waiting  #現在在等待的執行緒數量

barrier.abort()  #通知已經在等待的執行緒,不必再等了,我執行不到wait()了(任何一個執行緒執行都可以),當abort()方法被執行,wait()方法就會丟擲異常

5、semaphore (訊號量)

s = threading.Semaphore(2)

s.acquire() # 獲得,線上程中,也是鎖住某個變數,自己此刻獨佔;

輸出:True  # 成功鎖住

s.acquire(False) # 再來一次

輸出:True  # 成功鎖住

s.acquire(False) # 再來一次

輸出:False # 沒有鎖

鎖是訊號量的特例:為1的訊號量;

訊號量也是對共享資源的保護,但是和鎖不一樣,鎖限制只有一個執行緒可以訪問共享資源,而訊號量限制指定個執行緒可以訪問共享資源;所以訊號量可以用於做連線池的功能;

七、非同步程式設計

1、概念

同步、非同步:

發生在函式呼叫的時候,是否得到直接最終結果;

得到直接最終結果的是:同步呼叫;

不得到直接最終結果的是:非同步呼叫;

阻塞、非阻塞:

發生在函式呼叫的時候,是否立刻返回;

立刻返回:非阻塞呼叫;

不立刻返回:阻塞呼叫;

ps:同步、非同步 與 阻塞、非阻塞 在概念上是不相關的;

同步、非同步:關注的是結果;

阻塞、非阻塞:關注的是是否等待;

ps:非同步非阻塞是最好的效能咯;

同步IO、非同步IO、IO多路複用:

相關文章