當前位置:
首頁 > 知識 > 全棧工程師親自打造全面的多線程資料大全!從零到進階,謝謝大牛

全棧工程師親自打造全面的多線程資料大全!從零到進階,謝謝大牛

1. 多線程

概念:簡單地說操作系統可以同時執行多個不用程序。例如:一邊用瀏覽器上網,一邊在聽音樂,一邊在用筆記軟體記筆記。

並發:指的是任務數多餘cpu核數,通過操作系統的各種任務調度演算法,實現用多個任務「一起」執行(實際上總有一些任務不在執行,因為切換任務的熟度相當快,看上去一起執行而已)

並行:指的是任務數小於等於CPU核數,即任務真的是一起執行的。在給大家分享這篇多線程文章之前呢,我介紹一下我弄的一個學習交流群,有什麼不懂的問題,都可以在群里踴躍發言,需要啥資料隨時在群文件裡面獲取自己想要的資料。這個python群就是:643692991 小編期待大家一起進群交流討論,講實話還是一個非常適合學習的地方的。各種入門資料啊,進階資料啊,框架資料啊 爬蟲等等,都是有的,風裡雨里,真心歡迎熱愛Pytrhon的小夥伴進群。小編都在群里等你。

2. 線程

概念:線程是進程的一個實體,是CPU調度和分派的基本單位。

threading--單線程執行:

threading--多線程執行:

單線程與多線程比較

單線程要比多線程花費時間多

在創建完線程,需要調用start()方法來啟動

查看線程數量

線程執行代碼的封裝:

思考:定義一個新的子類class,只有繼承threading.Thead就可以,然後重寫run方法。

說明:threading.Thread類有一個run方法,用戶定義線程的功能函數,可以在自己的線程類中覆蓋該方法。而創建自己的線程實例後,通過Thread類的start方法,可以啟動該線程,當該線程獲得執行的機會時,就會調用run方法執行線程。

線程的狀態

多線程的執行順序是不確定的。當執行到sleep語句時,線程將被阻塞,到sleep結束後,線程進入就緒狀態,等待調度。而線程調度將自行選擇一個線程執行。

狀態:

(1) New 創建線程

(2) Runnable 就緒,等待調度

(3) Running 運行。

(4) Blocked 阻塞。阻塞可能在Wait Locked Sleeping

(5) Dead 消亡

線程中執行到阻塞,可能有三種情況:

同步:線程中獲取同步鎖,但是資源已經被其他線程鎖定時,進入Locked狀態,直到該資源可獲取(獲取的順序由Lock隊列控制)

睡眠:線程運行sleep()或join()方法後,線程進入Sleeping狀態。區別在於sleep等待固定的時間,而join是等待子線程執行完。當然join也可以指定一個「超時時間」。從語義上來說,如果兩個線程a,b, 在a中調用b.join(),相當於合併(join)成一個線程。最常見的情況是在主線程中join所有的子線程。

等待:線程中執行wait()方法後,線程進入Waiting狀態,等待其他線程的通知(notify)。

線程類型

線程有著不同的狀態,也有不同的類型:

主線程

子線程

守護線程(後台線程)

前台線程

多線程--共享全局變數問題

運行結果:

共享全局變數問題說明:

在一個進程內的所有線程共享全局變數,很方便在多個線程間共享數據。

缺點就是,線程是對全局變數隨意更改可能造成多線程之間對全局變數的混亂(即線程非安全)

如果多個線程它同時對同一個全局變數操作,會出現資源競爭問題,從而數據結果會不正確。

解決方案:

可以通過線程同步來進行解決線程同時修改全局變數的方式,在線程對全局變數進行修改時,都要先上鎖,處理完後再解鎖,在上鎖的整個過程中不允許其他線程訪問,就保證了數據的正確性。

3. 同步與互斥鎖

3.1 同步

如果多個線程共同對某個數據修改,則可能出現不可預料的結果,為了保證數據的正確性,需要對多個線程進行同步。

使用Tread對象的Lock和Rlock可以實現簡單的線程同步,這兩個對象都有acquire方法和release方法。對於那些需要每次只允許一個線程操作的數據,可以將其操作放到acquire和release方法之間。

3.2 互斥鎖

互斥鎖為資源引入一個狀態:鎖定/非鎖定

互斥鎖的作用:保證每次只有一個線程進行寫入操作,從而保證了多線程情況下數據的正確性。

threading 模塊中定義了Loack類,可以方便處理鎖定:

說明:鎖定方法acquirc 可以有一個blocking參數

如果設定blocking為True,則當前線程會阻塞,直到獲取到這個鎖為止(如果沒有指定,那麼默認為True)

如果設定blocking 為False,則當前線程不會阻塞。

上鎖解鎖的過程

當一個線程調用鎖的acquire()方法獲得鎖時,鎖就進入「locked」 狀態。

每次只有一個線程可以獲得鎖。如果此時另一個線程試圖獲得這個鎖,該線程就會變為「blocked」狀態,稱為「阻塞」,直到擁有鎖的線程調用鎖的release()方法釋放鎖之後,鎖進入「unlocked」狀態。

線程調度程序從處於同步阻塞狀態的線程中選擇一個來獲得鎖,並使得該線程進入運行(running)狀態。

鎖的好處:

確保了某段關鍵代碼只能由一個線程從頭到尾完整地執行

鎖的壞處:

阻止了多線程並發執行,包含鎖的某段代碼實際上只能以單線程模式執行,效率就大大地下降了

由於可以存在多個鎖,不同的線程持有不同的鎖,並試圖獲取對方持有的鎖時,可能會造成死鎖

死鎖

定義:在線程間共享多個資源的時候,如果兩個線程分別佔有一部分資源並且同時等待對方的資源,就會造成死鎖。

例子:

避免死鎖

程序設計時要盡量避免死鎖(銀行家演算法)

添加超時時間等。

4. 進程

定義:一個程序運行起來後,代碼和用到的資源稱之為進程。它是操作系統分配資源的基本單元。

4.1 進程的狀態

圖分析:

就緒態:運行的條件都已經慢去,正去等待cpu執行。

執行態:cpu正在執行其功能

等待態:等待某些條件滿足,例如一個程序sleep了,此時就處於等待態。

4.2 進程的創建

進程的創建實現例子:

multiprocessing模塊說明:multiprocessing模塊是多跨平台版本的多進程模塊,提供了一個Process類來代表一個進程對象,這個對象可以理解為是一個獨立的進程,可以執行另外的事情。

Process語法結構

Process([group [, target [, name [, args [, kwargs]]]]])

target:如果傳遞了函數的引用,可以任務這個子進程就執行這裡的代碼

args:給target指定的函數傳遞的參數,以元組的方式傳遞

kwargs:給target指定的函數傳遞命名參數

name:給進程設定一個名字,可以不設定

group:指定進程組,大多數情況下用不到

Process創建的實例對象的常用方法:

start():啟動子進程實例(創建子進程)

is_alive():判斷進程子進程是否還在活著

join([timeout]):是否等待子進程執行結束,或等待多少秒

terminate():不管任務是否完成,立即終止子進程

Process創建的實例對象的常用屬性:

name:當前進程的別名,默認為Process-N,N為從1開始遞增的整數

pid:當前進程的pid(進程號)

4.3 線程與進程的區別

定義的不同

進程是系統進行資源分配和調度的一個獨立單位。

線程是進程的一個實體,是CPU調度的基本單位。它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源.

區別:

一個程序至少有一個進程,一個進程至少有一個線程.

線程的劃分尺度小於進程(資源比進程少),使得多線程程序的並發性高。

進程在執行過程中擁有獨立的內存單元,而多個線程共享內 存,從而極大地提高了程序的運行效率

線線程不能夠獨立執行,必須依存在進程中

優缺點

線程和進程在使用上各有優缺點:線程執行開銷小,但不利於資源的管理和保護;而進程正相反。

5. 進程間通信--Queue可以使用multiprocessing模塊的Queue實現多進程之間的數據傳遞,Queue本身是一個消息列隊程序,首先用一個小實例來演示一下Queue的工作原理:

運行結果:

說明:

初始化Queue()對象時(例如:q=Queue()),若括弧中沒有指定最大可接收的消息數量,或數量為負值,那麼就代表可接受的消息數量沒有上限(直到內存的盡頭);

Queue.qsize():返回當前隊列包含的消息數量;

Queue.empty():如果隊列為空,返回True,反之False ;

Queue.full():如果隊列滿了,返回True,反之False;

Queue.get([block[, timeout]]):獲取隊列中的一條消息,然後將其從列隊中移除,block默認值為True;

1)如果block使用默認值,且沒有設置timeout(單位秒),消息列隊如果為空,此時程序將被阻塞(停在讀取狀態),直到從消息列隊讀到消息為止,

如果設置了timeout,則會等待timeout秒,若還沒讀取到任何消息,則拋出"Queue.Empty"異常;

2)如果block值為False,消息列隊如果為空,則會立刻拋出"Queue.Empty"異常;

Queue.get_nowait():相當Queue.get(False);

Queue.put(item,[block[, timeout]]):將item消息寫入隊列,block默認值為True;

1)如果block使用默認值,且沒有設置timeout(單位秒),消息列隊如果已經沒有空間可寫入,此時程序將被阻塞(停在寫入狀態),直到從消息列隊騰出空間為止,如果設置了timeout,則會等待timeout秒,若還沒空間,則拋出"Queue.Full"異常;

2)如果block值為False,消息列隊如果沒有空間可寫入,則會立刻拋出"Queue.Full"異常;

Queue.put_nowait(item):相當Queue.put(item, False);

Queue.put(item,[block[, timeout]]):將item消息寫入隊列,block默認值為True;

1)如果block使用默認值,且沒有設置timeout(單位秒),消息列隊如果已經沒有空間可寫入,此時程序將被阻塞(停在寫入狀態),直到從消息列隊騰出空間為止,如果設置了timeout,則會等待timeout秒,若還沒空間,則拋出"Queue.Full"異常;

2)如果block值為False,消息列隊如果沒有空間可寫入,則會立刻拋出"Queue.Full"異常;

Queue.put_nowait(item):相當Queue.put(item, False);

Queue實例

在父進程中創建兩個子進程,一個往Queue里寫數據,一個從Queue里讀數據

6. 進程池Pool

針對大量的目標,手動創建進程的工作量巨大,此時就可以用到multiprocessing模塊提供的Pool方法。

Pool過程說明:

初始化Pool時,可以指定一個最大進程數,當有新的請求提交到Pool中時,如果池還沒有滿,那麼就會創建一個新的進程用來執行該請求;但如果池中的進程數已經達到指定的最大值,那麼該請求就會等待,直到池中有進程結束,才會用之前的進程來執行新的任務,請看下面的實例:

運行結果:

multiprocessing.Pool常用函數解析:

apply_async(func[, args[, kwds]]) :使用非阻塞方式調用func(並行執行,堵塞方式必須等待上一個進程退出才能執行下一個進程),args為傳遞給func的參數列表,kwds為傳遞給func的關鍵字參數列表;

close():關閉Pool,使其不再接受新的任務;

terminate():不管任務是否完成,立即終止;

join():主進程阻塞,等待子進程的退出, 必須在close或terminate之後使用;

進程池中的Queue

要使用Pool創建進程,就需要使用multiprocessing.Manager()中的Queue(),而不是multiprocesing.Queue(),否則會得到一條如下的錯誤信息:

RuntimeError: Queue objects should only be shared between processes through inheritance.進程池中的進程通信:

運行結果:

好了。就到這裡,小夥伴們從中獲取到自己有用的東西嘛?

關注+轉發

感謝大家

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 python 的精彩文章:

Python誠可貴,愛情價更高
Python語法問題徵集
左手用R右手Python系列11——相關性分析
人工智慧時代,你真的不打算讓孩子學點Python防身?
高盛最新調查報告:Python和漢語,哪種語言在未來的國際社會上更重要?

TAG:python |