全棧工程師親自打造全面的多線程資料大全!從零到進階,謝謝大牛
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語法問題徵集
※左手用R右手Python系列11——相關性分析
※人工智慧時代,你真的不打算讓孩子學點Python防身?
※高盛最新調查報告:Python和漢語,哪種語言在未來的國際社會上更重要?
TAG:python |