Python学习笔记:多线程和多进程(转1)
前言
最近在學(xué)習(xí)深度學(xué)習(xí),已經(jīng)跑出了幾個(gè)模型,但Pyhton的基礎(chǔ)不夠扎實(shí),因此,開始補(bǔ)習(xí)Python了,大家都推薦廖雪峰的課程,因此,開始了學(xué)習(xí),但光學(xué)有沒有用,還要和大家討論一下,因此,寫下這些帖子,廖雪峰的課程連接在這里:廖雪峰
Python的相關(guān)介紹,以及它的歷史故事和運(yùn)行機(jī)制,可以參見這篇:python介紹
Python的安裝可以參見這篇:Python安裝
Python的運(yùn)行模式以及輸入輸出可以參見這篇:Python IO
Python的基礎(chǔ)概念介紹,可以參見這篇:Python 基礎(chǔ)
Python字符串和編碼的介紹,可以參見這篇:Python字符串與編碼
Python基本數(shù)據(jù)結(jié)構(gòu):list和tuple介紹,可以參見這篇:Python list和tuple
Python控制語(yǔ)句介紹:ifelse,可以參見這篇:Python 條件判斷
Python控制語(yǔ)句介紹:循環(huán)實(shí)現(xiàn),可以參見這篇:Python循環(huán)語(yǔ)句
Python數(shù)據(jù)結(jié)構(gòu):dict和set介紹Python數(shù)據(jù)結(jié)構(gòu)dict和set
Python函數(shù)相關(guān):Python函數(shù)
Python高階特性:Python高級(jí)特性
Python高階函數(shù):Python高階函數(shù)
Python匿名函數(shù):Python匿名函數(shù)
Python裝飾器:Python裝飾器
Python偏函數(shù):Python偏函數(shù)
Python模塊:Python模塊
Python面向?qū)ο缶幊?#xff08;1):Python面向?qū)ο?
Python面向?qū)ο缶幊?#xff08;2):Python面向?qū)ο?#xff08;2)
Python面向?qū)ο缶幊?#xff08;3):Python面向?qū)ο?#xff08;3)
Python面向?qū)ο缶幊?#xff08;4):Pyhton面向?qū)ο?#xff08;4)
Python面向?qū)ο蟾呒?jí)編程(上):Python面向?qū)ο蟾呒?jí)編程(上)
Python面向?qū)ο蟾呒?jí)編程(中上):Python面向?qū)ο蟾呒?jí)編程(中上)
Python面向?qū)ο蟾呒?jí)編程(中下):Python面向?qū)ο蟾呒?jí)編程(中下)
Python面向?qū)ο蟾呒?jí)編程(完):Python面向?qū)ο蟾呒?jí)編程(完)
Python錯(cuò)誤調(diào)試(起):Python調(diào)試:起
Python錯(cuò)誤調(diào)試(承):Python調(diào)試:承
Python錯(cuò)誤調(diào)試(轉(zhuǎn)):Python調(diào)試:轉(zhuǎn)
Python錯(cuò)誤調(diào)試(合):python調(diào)試:合
Python文件IO編程:Python文件IO
Python文件IO編程2:Python文件IO2
Python文件IO編程3:PYthon文件IO3
Python進(jìn)程和線程(起):Python進(jìn)程和線程起
Python進(jìn)程和線程(承):Python進(jìn)程和線程承
目錄:
- 前言
- Thread.local
- 小結(jié)
- 進(jìn)程Vs線程
- 線程切換
- 計(jì)算密集型 vs. IO密集型
- 異步IO
Thread.local
在多線程環(huán)境下,每個(gè)線程都有自己的數(shù)據(jù)。一個(gè)線程使用自己的局部變量比使用全局變量好,因?yàn)榫植孔兞恐挥芯€程自己能看見,不會(huì)影響其他線程,而全局變量的修改必須加鎖。
但是局部變量也有問題,就是在函數(shù)調(diào)用的時(shí)候,傳遞起來(lái)很麻煩:
每個(gè)函數(shù)一層一層調(diào)用都這么傳參數(shù)那還得了?用全局變量?也不行,因?yàn)槊總€(gè)線程處理不同的Student對(duì)象,不能共享。
如果用一個(gè)全局dict存放所有的Student對(duì)象,然后以thread自身作為key獲得線程對(duì)應(yīng)的Student對(duì)象如何?
global_dict = {}def std_thread(name):std = Student(name)# 把std放到全局變量global_dict中:global_dict[threading.current_thread()] = stddo_task_1()do_task_2()def do_task_1():# 不傳入std,而是根據(jù)當(dāng)前線程查找:std = global_dict[threading.current_thread()]...def do_task_2():# 任何函數(shù)都可以查找出當(dāng)前線程的std變量:std = global_dict[threading.current_thread()] ...這種方式理論上是可行的,它最大的優(yōu)點(diǎn)是消除了std對(duì)象在每層函數(shù)中的傳遞問題,但是,每個(gè)函數(shù)獲取std的代碼有點(diǎn)丑。
有沒有更簡(jiǎn)單的方式?
ThreadLocal應(yīng)運(yùn)而生,不用查找dict,ThreadLocal幫你自動(dòng)做這件事:
import threading# 創(chuàng)建全局ThreadLocal對(duì)象: local_school = threading.local()def process_student():# 獲取當(dāng)前線程關(guān)聯(lián)的student:std = local_school.studentprint('Hello, %s (in %s)' % (std, threading.current_thread().name))def process_thread(name):# 綁定ThreadLocal的student:local_school.student = nameprocess_student()t1 = threading.Thread(target= process_thread, args=('Alice',), name='Thread-A') t2 = threading.Thread(target= process_thread, args=('Bob',), name='Thread-B') t1.start() t2.start() t1.join() t2.join()執(zhí)行結(jié)果:
Hello, Alice (in Thread-A) Hello, Bob (in Thread-B)全局變量local_school就是一個(gè)ThreadLocal對(duì)象,每個(gè)Thread對(duì)它都可以讀寫student屬性,但互不影響。你可以把local_school看成全局變量,但每個(gè)屬性如local_school.student都是線程的局部變量,可以任意讀寫而互不干擾,也不用管理鎖的問題,ThreadLocal內(nèi)部會(huì)處理。
可以理解為全局變量local_school是一個(gè)dict,不但可以用local_school.student,還可以綁定其他變量,如local_school.teacher等等。
ThreadLocal最常用的地方就是為每個(gè)線程綁定一個(gè)數(shù)據(jù)庫(kù)連接,HTTP請(qǐng)求,用戶身份信息等,這樣一個(gè)線程的所有調(diào)用到的處理函數(shù)都可以非常方便地訪問這些資源。
小結(jié)
一個(gè)ThreadLocal變量雖然是全局變量,但每個(gè)線程都只能讀寫自己線程的獨(dú)立副本,互不干擾。ThreadLocal解決了參數(shù)在一個(gè)線程中各個(gè)函數(shù)之間互相傳遞的問題。
進(jìn)程Vs線程
我們介紹了多進(jìn)程和多線程,這是實(shí)現(xiàn)多任務(wù)最常用的兩種方式?,F(xiàn)在,我們來(lái)討論一下這兩種方式的優(yōu)缺點(diǎn)。
首先,要實(shí)現(xiàn)多任務(wù),通常我們會(huì)設(shè)計(jì)Master-Worker模式,Master負(fù)責(zé)分配任務(wù),Worker負(fù)責(zé)執(zhí)行任務(wù),因此,多任務(wù)環(huán)境下,通常是一個(gè)Master,多個(gè)Worker。
如果用多進(jìn)程實(shí)現(xiàn)Master-Worker,主進(jìn)程就是Master,其他進(jìn)程就是Worker。
如果用多線程實(shí)現(xiàn)Master-Worker,主線程就是Master,其他線程就是Worker。
多進(jìn)程模式最大的優(yōu)點(diǎn)就是穩(wěn)定性高,因?yàn)橐粋€(gè)子進(jìn)程崩潰了,不會(huì)影響主進(jìn)程和其他子進(jìn)程。(當(dāng)然主進(jìn)程掛了所有進(jìn)程就全掛了,但是Master進(jìn)程只負(fù)責(zé)分配任務(wù),掛掉的概率低)著名的Apache最早就是采用多進(jìn)程模式。
多進(jìn)程模式的缺點(diǎn)是創(chuàng)建進(jìn)程的代價(jià)大,在Unix/Linux系統(tǒng)下,用fork調(diào)用還行,在Windows下創(chuàng)建進(jìn)程開銷巨大。另外,操作系統(tǒng)能同時(shí)運(yùn)行的進(jìn)程數(shù)也是有限的,在內(nèi)存和CPU的限制下,如果有幾千個(gè)進(jìn)程同時(shí)運(yùn)行,操作系統(tǒng)連調(diào)度都會(huì)成問題。
多線程模式通常比多進(jìn)程快一點(diǎn),但是也快不到哪去,而且,多線程模式致命的缺點(diǎn)就是任何一個(gè)線程掛掉都可能直接造成整個(gè)進(jìn)程崩潰,因?yàn)樗芯€程共享進(jìn)程的內(nèi)存。在Windows上,如果一個(gè)線程執(zhí)行的代碼出了問題,你經(jīng)??梢钥吹竭@樣的提示:“該程序執(zhí)行了非法操作,即將關(guān)閉”,其實(shí)往往是某個(gè)線程出了問題,但是操作系統(tǒng)會(huì)強(qiáng)制結(jié)束整個(gè)進(jìn)程。
在Windows下,多線程的效率比多進(jìn)程要高,所以微軟的IIS服務(wù)器默認(rèn)采用多線程模式。由于多線程存在穩(wěn)定性的問題,IIS的穩(wěn)定性就不如Apache。為了緩解這個(gè)問題,IIS和Apache現(xiàn)在又有多進(jìn)程+多線程的混合模式,真是把問題越搞越復(fù)雜。
線程切換
無(wú)論是多進(jìn)程還是多線程,只要數(shù)量一多,效率肯定上不去,為什么呢?
我們打個(gè)比方,假設(shè)你不幸正在準(zhǔn)備中考,每天晚上需要做語(yǔ)文、數(shù)學(xué)、英語(yǔ)、物理、化學(xué)這5科的作業(yè),每項(xiàng)作業(yè)耗時(shí)1小時(shí)。
如果你先花1小時(shí)做語(yǔ)文作業(yè),做完了,再花1小時(shí)做數(shù)學(xué)作業(yè),這樣,依次全部做完,一共花5小時(shí),這種方式稱為單任務(wù)模型,或者批處理任務(wù)模型。
假設(shè)你打算切換到多任務(wù)模型,可以先做1分鐘語(yǔ)文,再切換到數(shù)學(xué)作業(yè),做1分鐘,再切換到英語(yǔ),以此類推,只要切換速度足夠快,這種方式就和單核CPU執(zhí)行多任務(wù)是一樣的了,以幼兒園小朋友的眼光來(lái)看,你就正在同時(shí)寫5科作業(yè)。
但是,切換作業(yè)是有代價(jià)的,比如從語(yǔ)文切到數(shù)學(xué),要先收拾桌子上的語(yǔ)文書本、鋼筆(這叫保存現(xiàn)場(chǎng)),然后,打開數(shù)學(xué)課本、找出圓規(guī)直尺(這叫準(zhǔn)備新環(huán)境),才能開始做數(shù)學(xué)作業(yè)。操作系統(tǒng)在切換進(jìn)程或者線程時(shí)也是一樣的,它需要先保存當(dāng)前執(zhí)行的現(xiàn)場(chǎng)環(huán)境(CPU寄存器狀態(tài)、內(nèi)存頁(yè)等),然后,把新任務(wù)的執(zhí)行環(huán)境準(zhǔn)備好(恢復(fù)上次的寄存器狀態(tài),切換內(nèi)存頁(yè)等),才能開始執(zhí)行。這個(gè)切換過程雖然很快,但是也需要耗費(fèi)時(shí)間。如果有幾千個(gè)任務(wù)同時(shí)進(jìn)行,操作系統(tǒng)可能就主要忙著切換任務(wù),根本沒有多少時(shí)間去執(zhí)行任務(wù)了,這種情況最常見的就是硬盤狂響,點(diǎn)窗口無(wú)反應(yīng),系統(tǒng)處于假死狀態(tài)。
所以,多任務(wù)一旦多到一個(gè)限度,就會(huì)消耗掉系統(tǒng)所有的資源,結(jié)果效率急劇下降,所有任務(wù)都做不好。
計(jì)算密集型 vs. IO密集型
是否采用多任務(wù)的第二個(gè)考慮是任務(wù)的類型。我們可以把任務(wù)分為計(jì)算密集型和IO密集型。
計(jì)算密集型任務(wù)的特點(diǎn)是要進(jìn)行大量的計(jì)算,消耗CPU資源,比如計(jì)算圓周率、對(duì)視頻進(jìn)行高清解碼等等,全靠CPU的運(yùn)算能力。這種計(jì)算密集型任務(wù)雖然也可以用多任務(wù)完成,但是任務(wù)越多,花在任務(wù)切換的時(shí)間就越多,CPU執(zhí)行任務(wù)的效率就越低,所以,要最高效地利用CPU,計(jì)算密集型任務(wù)同時(shí)進(jìn)行的數(shù)量應(yīng)當(dāng)?shù)扔贑PU的核心數(shù)。
計(jì)算密集型任務(wù)由于主要消耗CPU資源,因此,代碼運(yùn)行效率至關(guān)重要。Python這樣的腳本語(yǔ)言運(yùn)行效率很低,完全不適合計(jì)算密集型任務(wù)。對(duì)于計(jì)算密集型任務(wù),最好用C語(yǔ)言編寫。
第二種任務(wù)的類型是IO密集型,涉及到網(wǎng)絡(luò)、磁盤IO的任務(wù)都是IO密集型任務(wù),這類任務(wù)的特點(diǎn)是CPU消耗很少,任務(wù)的大部分時(shí)間都在等待IO操作完成(因?yàn)镮O的速度遠(yuǎn)遠(yuǎn)低于CPU和內(nèi)存的速度)。對(duì)于IO密集型任務(wù),任務(wù)越多,CPU效率越高,但也有一個(gè)限度。常見的大部分任務(wù)都是IO密集型任務(wù),比如Web應(yīng)用。
IO密集型任務(wù)執(zhí)行期間,99%的時(shí)間都花在IO上,花在CPU上的時(shí)間很少,因此,用運(yùn)行速度極快的C語(yǔ)言替換用Python這樣運(yùn)行速度極低的腳本語(yǔ)言,完全無(wú)法提升運(yùn)行效率。對(duì)于IO密集型任務(wù),最合適的語(yǔ)言就是開發(fā)效率最高(代碼量最少)的語(yǔ)言,腳本語(yǔ)言是首選,C語(yǔ)言最差。
異步IO
考慮到CPU和IO之間巨大的速度差異,一個(gè)任務(wù)在執(zhí)行的過程中大部分時(shí)間都在等待IO操作,單進(jìn)程單線程模型會(huì)導(dǎo)致別的任務(wù)無(wú)法并行執(zhí)行,因此,我們才需要多進(jìn)程模型或者多線程模型來(lái)支持多任務(wù)并發(fā)執(zhí)行。
現(xiàn)代操作系統(tǒng)對(duì)IO操作已經(jīng)做了巨大的改進(jìn),最大的特點(diǎn)就是支持異步IO。如果充分利用操作系統(tǒng)提供的異步IO支持,就可以用單進(jìn)程單線程模型來(lái)執(zhí)行多任務(wù),這種全新的模型稱為事件驅(qū)動(dòng)模型,Nginx就是支持異步IO的Web服務(wù)器,它在單核CPU上采用單進(jìn)程模型就可以高效地支持多任務(wù)。在多核CPU上,可以運(yùn)行多個(gè)進(jìn)程(數(shù)量與CPU核心數(shù)相同),充分利用多核CPU。由于系統(tǒng)總的進(jìn)程數(shù)量十分有限,因此操作系統(tǒng)調(diào)度非常高效。用異步IO編程模型來(lái)實(shí)現(xiàn)多任務(wù)是一個(gè)主要的趨勢(shì)。
對(duì)應(yīng)到Python語(yǔ)言,單線程的異步編程模型稱為協(xié)程,有了協(xié)程的支持,就可以基于事件驅(qū)動(dòng)編寫高效的多任務(wù)程序。我們會(huì)在后面討論如何編寫協(xié)程。
總結(jié)
以上是生活随笔為你收集整理的Python学习笔记:多线程和多进程(转1)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: docker mysql数据库初始化_如
- 下一篇: Python学习笔记:线程和进程(合),