進(jìn)程以及狀態(tài)
之前說(shuō)過(guò)線程精钮,這回我們聊進(jìn)程帽借,他們有一定相似性,線程是CPU調(diào)度的基本單位谷遂,進(jìn)程是資源分配的基本單位葬馋,也是線程的容器(進(jìn)程包含線程)
比如我們運(yùn)行了飛Q程序,打開任務(wù)管理器肾扰,就會(huì)看到飛Q在執(zhí)行畴嘶,還顯示了CPU占用,內(nèi)存占用集晚,硬盤占用以及網(wǎng)絡(luò)占用窗悯,所以,程序是固定不變的偷拔,進(jìn)程會(huì)跟據(jù)操作系統(tǒng)進(jìn)行系統(tǒng)資源分配
程序是靜態(tài)的蒋院,但運(yùn)行起來(lái)亏钩,代碼加用到的資源成為進(jìn)程。一個(gè)進(jìn)程中包括多個(gè)線程(線程是輕量級(jí)的進(jìn)程)
進(jìn)程運(yùn)行有幾個(gè)狀態(tài)欺旧,比如說(shuō)并發(fā)的時(shí)候姑丑,cpu核小于任務(wù)數(shù),比如圖中的3任務(wù)辞友,1個(gè)CPU栅哀,由于CPU是輪循的,執(zhí)行1的一部分称龙,1的進(jìn)程就在運(yùn)行狀態(tài)留拾,這時(shí)切換到2任務(wù),2進(jìn)入運(yùn)行鲫尊,1之前的準(zhǔn)備資源完畢進(jìn)入就緒狀態(tài)痴柔。什么時(shí)候是進(jìn)入等待狀態(tài)呢,比如2遇到了time.sleep這時(shí)就會(huì)進(jìn)入阻塞狀態(tài)疫向,同時(shí)開始3任務(wù)的運(yùn)行狀態(tài)咳蔚。哪個(gè)任務(wù)執(zhí)行完畢就進(jìn)入死亡狀態(tài)。如果下次2運(yùn)行到就緒狀態(tài)鸿捧,下次輪詢到就可以到運(yùn)行狀態(tài)屹篓。比如突然來(lái)了4任務(wù)疙渣,那這個(gè)任務(wù)就是新建啟動(dòng)進(jìn)入就緒狀態(tài)
進(jìn)程基本使用
進(jìn)程和線程代碼又類似之處
1 導(dǎo)入模塊import multiprocessing
2創(chuàng)建進(jìn)程對(duì)象mulitprocessing.Process(target=xxx)
3實(shí)例對(duì)象啟動(dòng)start()
上圖創(chuàng)建一個(gè)進(jìn)程代碼匙奴,我們使用多進(jìn)程來(lái)實(shí)現(xiàn),這里需要提出的是妄荔,進(jìn)了程序執(zhí)行的是主進(jìn)程泼菌,而實(shí)例對(duì)象開啟了一個(gè)子進(jìn)程。注意這里創(chuàng)建子進(jìn)程必須使用if __name__=='__main__':否則會(huì)提示報(bào)錯(cuò)
為了體現(xiàn)主進(jìn)程和子進(jìn)程在(幾乎)同時(shí)運(yùn)行啦租,我們改成如上代碼(的確是cpu輪詢效果)
Process有如上圖的語(yǔ)法哗伯,創(chuàng)建時(shí),可以傳入target,args,kwargs和線程一樣篷角,可以給線程起名字焊刹,指定組。還有圖中的方法
進(jìn)程的名稱恳蹲,pid】
進(jìn)程名稱獲扰翱椤(其實(shí)是獲得當(dāng)前進(jìn)程)
之前的代碼加上打印當(dāng)前線程,就可以看到主進(jìn)程和運(yùn)行的子進(jìn)程
如果我們對(duì)進(jìn)程實(shí)例的時(shí)候加上了name參數(shù)嘉蕾,就會(huì)打印出指定的名字
獲得當(dāng)前進(jìn)程有pid(process id)屬性獲得其編號(hào)贺奠,我們可以打印出主進(jìn)程和子進(jìn)程的編號(hào)
獲得編號(hào)的另一種方法,調(diào)用os模塊错忱,os.getpid()可以獲得當(dāng)前進(jìn)程id
獲得父進(jìn)程的id儡率,仍是用os模塊挂据,os,getppid()獲得父進(jìn)程的id,上圖子進(jìn)程的父進(jìn)程就是主進(jìn)程
我們獲得進(jìn)程id有什么用儿普,linux終端我們可以使用kill -9 進(jìn)程編號(hào) ?來(lái)結(jié)束進(jìn)程
比如上圖我們殺掉子進(jìn)程kill -9 34968就會(huì)有如上結(jié)果崎逃,并沒(méi)運(yùn)行10次
我們殺掉主進(jìn)程,會(huì)得到pycharm的如上提示眉孩,同樣結(jié)束掉程序
進(jìn)程參數(shù)傳遞婚脱,全局變量
子進(jìn)程參數(shù)傳遞和子線程一致,通過(guò)args=元組勺像,kwargs=字典 ? 來(lái)傳參
進(jìn)程之間不能實(shí)現(xiàn)全局變量共享障贸,如果聲明,也只是將其copy一份(子進(jìn)程會(huì)復(fù)制主進(jìn)程資源)
守護(hù)主進(jìn)程
守護(hù)主進(jìn)程吟宦,主進(jìn)程結(jié)束時(shí)篮洁,子進(jìn)程也結(jié)束
我們寫出如上代碼,會(huì)發(fā)現(xiàn)主進(jìn)程結(jié)束了殃姓,但是子進(jìn)程還是沒(méi)有結(jié)束袁波。
我們想主進(jìn)程結(jié)束子進(jìn)程也跟著結(jié)束,需要使用實(shí)例屬性賦值Process.daemon=True(和線程不一樣蜗侈,線程使用setDaemon方法)篷牌,如下圖
另一種方法,在主進(jìn)程結(jié)束前使用實(shí)例Process.terminate()結(jié)束子進(jìn)程
進(jìn)程和線程的對(duì)比
功能上區(qū)別
2使用區(qū)別
比如上圖內(nèi)存執(zhí)行著網(wǎng)易云音樂(lè)和天天靜聽的2個(gè)進(jìn)程踏幻,每個(gè)進(jìn)程都有下載和播放的線程枷颊,下載和播放線程之間共享著歌曲讀寫資源,但是進(jìn)程間沒(méi)有資源共享该面,互不影響夭苗,所以進(jìn)程會(huì)更穩(wěn)定。
線程不能單獨(dú)運(yùn)行隔缀,必須存在于進(jìn)程中
對(duì)比圖
選擇原則
頻繁創(chuàng)建優(yōu)先線程题造,消耗資源小,切換速度快猾瘸,大量計(jì)算使用線程界赔,多機(jī)分布多進(jìn)程,多核多線程牵触,安全角度可能選擇進(jìn)程淮悼,速度角度選擇線程,都滿足荒吏,那就哪個(gè)熟悉拿手就行敛惊。但是cpython有GIL鎖,當(dāng)IO操作達(dá)到一定數(shù)量才會(huì)釋放绰更,造成多核cpu瞧挤,多線程也是分時(shí)切換锡宋。
CPU密集型,進(jìn)程優(yōu)先特恬,IO密集型执俩,線程優(yōu)先
消息隊(duì)列一基本操作
由于進(jìn)程間本身并不能共享資源,我們使用隊(duì)列容器癌刽,來(lái)給多進(jìn)程實(shí)現(xiàn)數(shù)據(jù)傳遞
隊(duì)列如上圖役首,一段發(fā)數(shù)據(jù),另一端取數(shù)據(jù)显拜,放入值put,取出值get
上圖簡(jiǎn)單代碼實(shí)現(xiàn)queue傳入消息衡奥,實(shí)例時(shí)需要指定隊(duì)列長(zhǎng)度,上圖放置完運(yùn)行沒(méi)問(wèn)題远荠。
當(dāng)我們嘗試再加入一個(gè)消息時(shí)矮固,發(fā)現(xiàn)程序并沒(méi)有結(jié)束,其實(shí)這時(shí)是進(jìn)入了阻塞狀態(tài)譬淳,因?yàn)樽畲箝L(zhǎng)度為3档址,新消息進(jìn)入,需要一個(gè)消息發(fā)送出
我們把超過(guò)隊(duì)列長(zhǎng)度的最后一個(gè)改成方法put_nowait邻梆,運(yùn)行直接報(bào)錯(cuò)守伸,這個(gè)方法會(huì)不阻塞等待,直接報(bào)錯(cuò)
我們可以將數(shù)據(jù)取出來(lái)打印浦妄,會(huì)發(fā)現(xiàn)先放進(jìn)去的先取出來(lái)
如果我們想多取一個(gè)尼摹,程序就會(huì)進(jìn)入阻塞狀態(tài),等待隊(duì)列傳入才能取出
同樣我們可以使用get_nowait方法校辩,這樣當(dāng)讀不到消息就會(huì)報(bào)錯(cuò)窘问,queue.Empty提示隊(duì)列已空
消息隊(duì)列一常見(jiàn)判斷
上次結(jié)尾列舉了就幾個(gè)方法辆童,這里我們調(diào)用下宜咒,Queue的方法empty和full判斷是否空滿,注意方法要加括號(hào)把鉴,返回Bool值故黑。qsize方法返回隊(duì)列的消息長(zhǎng)度值
注意仔細(xì)看圖中會(huì)發(fā)現(xiàn)有個(gè)坑,即放滿了隊(duì)列后庭砍,empty返回的結(jié)果也是True
看了官方文檔的empty和full解釋场晶,的確也都加了not reliable不可信的解釋,這是為什么呢怠缸,因?yàn)槲覀冏x取狀態(tài)的時(shí)候诗轻,寫入可能沒(méi)有那么快,如果加入一定的延時(shí)如time.sleep可能就會(huì)使數(shù)據(jù)準(zhǔn)確存放
Queue實(shí)現(xiàn)進(jìn)程間的數(shù)據(jù)共享
進(jìn)程通信思路:進(jìn)程a寫入put揭北,進(jìn)程b讀取get
如上圖扳炬,我們編寫2個(gè)進(jìn)程分別從隊(duì)列中讀寫數(shù)據(jù)吏颖,會(huì)發(fā)現(xiàn)讀的順序快了,結(jié)果沒(méi)寫入就讀完了
為了實(shí)現(xiàn)能讀取成功恨樟,我們使用進(jìn)程的jion方法半醉,讓寫先完成,就出現(xiàn)如上結(jié)果劝术,其實(shí)相當(dāng)于單進(jìn)程
進(jìn)程池
我們?yōu)槭裁词褂眠M(jìn)程池缩多,當(dāng)我們創(chuàng)建進(jìn)程數(shù)量不多時(shí),可以用Process實(shí)例來(lái)生成养晋,當(dāng)進(jìn)程數(shù)量大到千百個(gè)量級(jí)以上衬吆,我們就可以使用Pool方法創(chuàng)建生成多個(gè)進(jìn)程的方法
Pool根據(jù)請(qǐng)求創(chuàng)建進(jìn)程,初始設(shè)定進(jìn)程上限绳泉,如果沒(méi)到上限咆槽,有請(qǐng)求就繼續(xù)創(chuàng)建,如果達(dá)到上限圈纺,就會(huì)等待池中進(jìn)程有結(jié)束
Pool類實(shí)例核心方法
apply()?同步方式執(zhí)行,池子里預(yù)先創(chuàng)建3個(gè)進(jìn)程秦忿,比如上圖3個(gè)文件拷貝,我們先啟動(dòng)進(jìn)程1蛾娶,用于拷貝文件1灯谣,結(jié)束后啟動(dòng)進(jìn)程2,拷貝文件2蛔琅,至啟動(dòng)進(jìn)程3拷貝文件3胎许,如果還有第四個(gè)任務(wù),就會(huì)等進(jìn)程池有進(jìn)程結(jié)束罗售,利用這個(gè)進(jìn)程啟動(dòng)新任務(wù)(同一時(shí)間只有一個(gè)進(jìn)程執(zhí)行)
apply_async() 異步方式?如上圖三個(gè)任務(wù)辜窑,三個(gè)進(jìn)程同時(shí)啟動(dòng),一起完成任務(wù)寨躁,如果還有第四個(gè)任務(wù)穆碎,就會(huì)等進(jìn)程池有進(jìn)程結(jié)束,利用這個(gè)進(jìn)程啟動(dòng)新任務(wù)
代碼應(yīng)用职恳,模擬同步拷貝文件
代碼說(shuō)明所禀,使用進(jìn)程池,設(shè)置進(jìn)程池最大進(jìn)程數(shù)放钦,執(zhí)行使用pool.apply()色徘,apply有3個(gè)參數(shù),func傳入函數(shù)操禀,args,kwargs之前進(jìn)程實(shí)例也接觸過(guò)褂策,是用于參數(shù)傳遞。這里我們先不傳參,在每個(gè)進(jìn)程打印當(dāng)前進(jìn)程斤寂,會(huì)發(fā)現(xiàn)10個(gè)任務(wù)不是像我們想象中的1231231231這樣的蔑水,其實(shí)是我修改了sleep時(shí)間,如果sleep時(shí)間長(zhǎng)會(huì)形成類似順序執(zhí)行的結(jié)構(gòu)扬蕊,但是由于資源沒(méi)釋放資源準(zhǔn)備等因素搀别,進(jìn)程池自身管理每個(gè)任務(wù)哪個(gè)進(jìn)程來(lái)執(zhí)行
一旦進(jìn)程池創(chuàng)建,他們的pid就固定了尾抑,所以新任務(wù)來(lái)不是新創(chuàng)建線程歇父,而是讓空閑的進(jìn)程執(zhí)行新的任務(wù)
我們用過(guò)同步,就會(huì)摩拳擦掌來(lái)試試異步了再愈,我們先把上面代碼apply換成apply_async來(lái)試試榜苫,哎,怎么什么結(jié)果都沒(méi)有就完事啦
我們?cè)趺床拍茏尞惒綄?shí)現(xiàn)呢翎冲,上面的問(wèn)題是怎么回事垂睬,這里我們需要注意的是,異步進(jìn)程池比同步進(jìn)程池需要多2步操作抗悍,第一部是pool.close()這里關(guān)閉并不是不執(zhí)行了驹饺,而是不再接收新的額外任務(wù)了,第二個(gè)是加上pool.join()缴渊,之前直接結(jié)束時(shí)因?yàn)槭褂卯惒缴鸵迹鬟M(jìn)程就默認(rèn)不會(huì)等待子進(jìn)程結(jié)束,主進(jìn)程結(jié)束衔沼,pool也就銷毀了蝌借,于是池里的任務(wù)不會(huì)執(zhí)行,我們使用join指蚁,這樣主進(jìn)程就會(huì)等待進(jìn)程池任務(wù)都結(jié)束再結(jié)束菩佑。例中代碼改慢sleep時(shí)間,會(huì)發(fā)現(xiàn)3個(gè)3個(gè)顯示
進(jìn)程池的Queue
標(biāo)題即開始講進(jìn)程池間進(jìn)程的通信
實(shí)例方法queue=multiprocessing.Manager().Queue(3)
比如我們實(shí)現(xiàn)個(gè)同步寫入讀取
我們實(shí)現(xiàn)異步寫入讀取凝化,當(dāng)然還是不要忘了稍坯,close,join
按照視頻中代碼,我們使用異步缘圈,并沒(méi)有實(shí)現(xiàn)我們想要的功能劣光,(各人感覺(jué)還不如充分利用queue讀寫的阻塞功能),這是因?yàn)閜ool分配資源糟把,先準(zhǔn)備好了讀的功能,所以沒(méi)讀到(即使你write寫在read前apply)牲剃,這時(shí)我們?cè)趺崔k呢
apply_async異步函數(shù)會(huì)返回一個(gè)對(duì)象遣疯,可以pycharm查看源碼,使用對(duì)象的wait功能凿傅,就會(huì)實(shí)現(xiàn)類似join的功能缠犀,其實(shí)這樣也變相變成了單進(jìn)程数苫,先寫完再讀
案例,多進(jìn)程文件復(fù)制
視頻教程是使用讀寫進(jìn)程辨液,這里直接使用shutil.copy(src,dst)來(lái)實(shí)現(xiàn)
這里需要注意的是虐急,開始我嘗試在進(jìn)程函數(shù)內(nèi)直接使用filelist,source_dir,tar_dir這幾個(gè)參數(shù),函數(shù)并沒(méi)有報(bào)錯(cuò)滔迈,但是也沒(méi)有執(zhí)行下去止吁,直接結(jié)束了,所以記得一定要把這些引用的看似全局的參數(shù)提供給子線程燎悍,
多進(jìn)程版web服務(wù)器
具體代碼不用詳解了敬惦,這里用的是封裝過(guò)的代碼,需要注意的是多進(jìn)程引用的函數(shù)是request_handler谈山,我們傳入了new_socket(accept返回)俄删,在函數(shù)里已經(jīng)close掉,但是我們多線程創(chuàng)建完還是得close一遍奏路,這就是因?yàn)樽舆M(jìn)程對(duì)new_socket拷貝了一份畴椰,引用計(jì)數(shù)相當(dāng)為2,當(dāng)函數(shù)內(nèi)close鸽粉,相當(dāng)于引用計(jì)數(shù)變成1迅矛,但是還沒(méi)有徹底關(guān)閉new_socket,還需要再close一回
可迭代對(duì)象
為什么學(xué)習(xí):可迭代對(duì)象-》迭代器-》生成器-》協(xié)程
我們可以導(dǎo)入collections的Iterable類潜叛,用isinstance來(lái)判斷是否可迭代
我們隨便創(chuàng)建一個(gè)類對(duì)象秽褒,會(huì)發(fā)現(xiàn)是不可可迭代的
當(dāng)我們給類添加iter魔法方法,這個(gè)類的實(shí)例就是可迭代的威兜。
所以對(duì)象所屬類有__iter__方法销斟,即是可迭代對(duì)象
迭代器和使用方法
iter獲得迭代器,多次使用next逐個(gè)獲得迭代器元素
迭代器可以記錄遍歷的位置椒舵,配合next獲得下個(gè)元素(如果迭代器到頭蚂踊,再next會(huì)引發(fā)StopIteration錯(cuò)誤)
for循環(huán)本質(zhì),取得可迭代對(duì)象的迭代器笔宿,每次調(diào)用獲得next迭代器的內(nèi)容犁钟,遇到StopIteratioin異常停止
自定義迭代器需要類定義iter,next這2個(gè)魔法方法