黄色网页视频 I 影音先锋日日狠狠久久 I 秋霞午夜毛片 I 秋霞一二三区 I 国产成人片无码视频 I 国产 精品 自在自线 I av免费观看网站 I 日本精品久久久久中文字幕5 I 91看视频 I 看全色黄大色黄女片18 I 精品不卡一区 I 亚洲最新精品 I 欧美 激情 在线 I 人妻少妇精品久久 I 国产99视频精品免费专区 I 欧美影院 I 欧美精品在欧美一区二区少妇 I av大片网站 I 国产精品黄色片 I 888久久 I 狠狠干最新 I 看看黄色一级片 I 黄色精品久久 I 三级av在线 I 69色综合 I 国产日韩欧美91 I 亚洲精品偷拍 I 激情小说亚洲图片 I 久久国产视频精品 I 国产综合精品一区二区三区 I 色婷婷国产 I 最新成人av在线 I 国产私拍精品 I 日韩成人影音 I 日日夜夜天天综合

【python】python進程、線程、協(xié)程和什么時候使用

系統(tǒng) 1893 0
原文鏈接: https://blog.csdn.net/bandaoyu/article/details/90583629

(現(xiàn)在人工智能非常火爆,很多朋友都想學(xué),但是一般的教程都是為博碩生準(zhǔn)備的,太難看懂了。最近發(fā)現(xiàn)了一個非常適合小白入門的教程,不僅通俗易懂而且還很風(fēng)趣幽默。所以忍不住分享一下給大家。

? 點這里https://www.cbedai.net/ialexanderi可以跳轉(zhuǎn)到教程。)

說明
進程:是操作系統(tǒng)進行資源分配的最小單元,資源包括CPU、內(nèi)存、磁盤等IO設(shè)備等等

線程:是CPU調(diào)度的基本單位。

進程:系統(tǒng)分配資源的載體,是程序運行的實例;

線程:程序執(zhí)行的最小單元,是進程中的一個實體用來執(zhí)行程序,一個進程中有多個線程。

為什么有人說 Python 多線程是雞肋?
?

在我們常識中,多進程、多線程都是通過并發(fā)的方式充分利用硬件資源提高程序的運行效率,怎么在 Python 中反而成了雞肋?

因為 Python 中臭名昭著的 GIL。

那么 GIL 是什么?為什么會有 GIL?多線程真的是雞肋嗎? GIL 可以去掉嗎?

多線程是不是雞肋:

做個實驗:

將數(shù)字 “1億” 遞減,減到 0 程序就終止,這個任務(wù)如果我們使用單線程來執(zhí)行,完成時間會是多少?

單線程,4核 CPU 計算機中,單線程所花的時間是 6.5 秒。

多線程創(chuàng)建兩個子線程 t1、t2,每個線程各執(zhí)行 5 千萬次減操作,兩個線程以合作的方式執(zhí)行是 6.8 秒,反而變慢了。

?

按理來說,兩個線程同時并行地運行,時間應(yīng)該不減反增。原因就在于 GIL ,

在Python多線程下,每個線程的執(zhí)行方式:

1.獲取GIL

2.執(zhí)行代碼直到sleep或者是python虛擬機將其掛起。

3.釋放GIL

可見,某個線程想要執(zhí)行,必須先拿到GIL,我們可以把GIL看作是“通行證”,并且在一個python進程中,GIL只有一個。拿不到通行證的線程,就不允許進入CPU執(zhí)行。

?

?

在 Cpython 解釋器(Python語言的主流解釋器)中,有一把全局解釋鎖(Global Interpreter Lock),在解釋器解釋執(zhí)行 Python 代碼時,先要得到這把鎖,意味著,任何時候只可能有一個線程在執(zhí)行代碼,其它線程要想獲得 CPU 執(zhí)行代碼指令,就必須先獲得這把鎖,如果鎖被其它線程占用了,那么該線程就只能等待,直到占有該鎖的線程釋放鎖才有執(zhí)行代碼指令的可能。

同一時刻,只有一個線程在運行,其它線程只能等待,即使是多核CPU,也沒辦法讓多個線程「并行」地同時執(zhí)行代碼,只能是交替執(zhí)行,因為多線程涉及到上線文切換、鎖機制處理(獲取鎖,釋放鎖等),所以,多線程執(zhí)行不快反慢。

什么時候 GIL 被釋放呢?

當(dāng)一個線程遇到 I/O 任務(wù)時,將釋放GIL 計算密集型(CPU-bound)線程執(zhí)行 100 次解釋器的計步(ticks)時(計步可粗略看作 Python 虛擬機的指令),也會釋放 GIL。

可以通過設(shè)置計步長度,查看計步長度。相比單線程,這些多是多線程帶來的額外開銷。

???????CPython 解釋器為什么要這樣設(shè)計?多線程有個問題,怎么解決共享數(shù)據(jù)的同步、一致性問題,因為,對于多個線程訪問共享數(shù)據(jù)時,可能有兩個線程同時修改一個數(shù)據(jù)情況,如果沒有合適的機制保證數(shù)據(jù)的一致性,那么程序最終導(dǎo)致異常,所以,Python之父就搞了個全局的線程鎖,不管你數(shù)據(jù)有沒有同步問題,反正一刀切,上個全局鎖,保證數(shù)據(jù)安全。這也就是多線程雞肋的原因,因為它沒有細粒度的控制數(shù)據(jù)的安全,而是用一種簡單粗暴的方式來解決。

??????這種解決辦法放在90年代,其實是沒什么問題的,畢竟,那時候的硬件配置還很簡陋,單核 CPU 還是主流,多線程的應(yīng)用場景也不多,大部分時候還是以單線程的方式運行,單線程不要涉及線程的上下文切換,效率反而比多線程更高(在多核環(huán)境下,不適用此規(guī)則)。

??????所以,采用 GIL 的方式來保證數(shù)據(jù)的一致性和安全,未必不可取,至少在當(dāng)時是一種成本很低的實現(xiàn)方式。那么把 GIL 去掉可行嗎?還真有人這么干多,但是結(jié)果令人失望,在1999年Greg Stein 和Mark Hammond 兩位哥們就創(chuàng)建了一個去掉 GIL 的 Python 分支,在所有可變數(shù)據(jù)結(jié)構(gòu)上把 GIL 替換為更為細粒度的鎖。然而,做過了基準(zhǔn)測試之后,去掉GIL的 Python 在單線程條件下執(zhí)行效率將近慢了2倍。

Python之父表示:基于以上的考慮,去掉GIL沒有太大的價值而不必花太多精力。小結(jié)CPython解釋器提供 GIL 保證線程數(shù)據(jù)同步,那么有了 GIL,我們還需要線程同步嗎?多線程在 IO 密集型任務(wù)中,表現(xiàn)又是怎樣呢?歡迎大家留言。

?
python的多線程到底有沒有用?
?

?

1、CPU密集型代碼(各種循環(huán)處理、計數(shù)等等),在這種情況下, ticks計數(shù)很快就會達到閾值,然后觸發(fā)GIL的釋放與再競爭(多個線程來回切換當(dāng)然是需要消耗資源的),所以python下的多線程對CPU密集型代碼并不友好。
2、IO密集型代碼(文件處理、網(wǎng)絡(luò)爬蟲等),多線程能夠有效提升效率(單線程下有IO操作會進行IO等待,造成不必要的時間浪費,而開啟多線程能在線程A等待時,自動切換到線程B,可以不浪費CPU的資源,從而能提升程序執(zhí)行效率)。 所以python的多線程對IO密集型代碼比較友好。

而在python3.x中,GIL不使用ticks計數(shù),改為使用計時器(執(zhí)行時間達到閾值后,當(dāng)前線程釋放GIL),這樣對CPU密集型程序更加友好,但依然沒有解決GIL導(dǎo)致的同一時間只能執(zhí)行一個線程的問題,所以效率依然不盡如人意。

多核多線程比單核多線程更差,原因是單核下多線程,每次釋放GIL,喚醒的那個線程都能獲取到GIL鎖,所以能夠無縫執(zhí)行,但多核下,CPU0釋放GIL后,其他CPU上的線程都會進行競爭,但GIL可能會馬上又被CPU0拿到,導(dǎo)致其他幾個CPU上被喚醒后的線程會醒著等待到切換時間后又進入待調(diào)度狀態(tài),這樣會造成線程顛簸(thrashing),導(dǎo)致效率更低

回到最開始的問題:經(jīng)常我們會聽到老手說:“python下想要充分利用多核CPU,就用多進程”,原因是什么呢?
原因是:每個進程有各自獨立的GIL,互不干擾,這樣就可以真正意義上的并行執(zhí)行,所以在python中,多進程的執(zhí)行效率優(yōu)于多線程(僅僅針對多核CPU而言)。

所以在這里說結(jié)論:多核下,想做并行提升效率,比較通用的方法是使用多進程,能夠有效提高執(zhí)行效率

?

話說回來,CPU密集型的程序用python來做,本身就不合適。跟C,Go,Java的速度比,實在性能差到?jīng)]法說。你當(dāng)然可以寫個C擴展來實現(xiàn)真正的多線程,用python來調(diào)用,那樣速度是快。我們之所以用python來做,只是因為開發(fā)效率超高,可以快速實現(xiàn)。

最后補充幾點:

python中要想利用好CPU,還是用多進程來做吧。或者,可以使用協(xié)程。multiprocessing和gevent在召喚你。
GIL不是bug,Guido也不是水平有限才留下這么個東西。龜叔曾經(jīng)說過,嘗試不用GIL而用其他的方式來做線程安全,結(jié)果python語言整體效率又下降了一倍,權(quán)衡利弊,GIL是最好的選擇——不是去不掉,而是故意留著的。
想讓python計算速度快起來,又不想寫C?用pypy吧,這才是真正的大殺器。
?

不適合用多線程的情況下用多進程還是協(xié)程提高并發(fā)能力?
一、多進程能夠更好的利用多核CPU。

????但是多進程也有其自己的限制:相比線程更加笨重、切換耗時更長,并且在python的多進程下,進程數(shù)量不推薦超過CPU核心數(shù)(一個進程只有一個GIL,所以一個進程只能跑滿一個CPU),因為一個進程占用一個CPU時能充分利用機器的性能,但是進程多了就會出現(xiàn)頻繁的進程切換,反而得不償失。

所以多核的情況下,考慮線程數(shù)與 CPU核心數(shù)相同的多線程,充分利用CPU的多核能力。

?

二、什么時候需要協(xié)程?

不過特殊情況(特指IO密集型任務(wù))下,多線程是比多進程好用的。

舉個例子:給你200W條url,需要你把每個url對應(yīng)的頁面抓取保存起來,這種時候,單單使用多進程,效果肯定是很差的。為什么呢?

例如每次請求的等待時間是2秒,那么如下(忽略cpu計算時間):

1、單進程+單線程:需要2秒*200W=400W秒==1111.11個小時==46.3天,這個速度明顯是不能接受的

2、單進程+多線程:例如我們在這個進程中開了10個多線程,比1中能夠提升10倍速度,也就是大約4.63天能夠完成200W條抓取,請注意,這里的實際執(zhí)行是:線程1遇見了阻塞,CPU切換到線程2去執(zhí)行,遇見阻塞又切換到線程3等等,10個線程都阻塞后,這個進程就阻塞了,而直到某個線程阻塞完成后,這個進程才能繼續(xù)執(zhí)行,所以速度上提升大約能到10倍(這里忽略了線程切換帶來的開銷,實際上的提升應(yīng)該是不能達到10倍的),但是需要考慮的是線程的切換也是有開銷的,所以不能無限的啟動多線程(開200W個線程肯定是不靠譜的)

3、多進程+多線程:這里就厲害了,一般來說也有很多人用這個方法,多進程下,每個進程都能占一個cpu,而多線程從一定程度上繞過了阻塞的等待,所以比單進程下的多線程又更好使了,例如我們開10個進程,每個進程里開20W個線程,執(zhí)行的速度理論上是比單進程開200W個線程快10倍以上的(為什么是10倍以上而不是10倍,主要是cpu切換200W個線程的消耗肯定比切換20W個線程進程大得多,考慮到這部分開銷,所以是10倍以上)。

還有更好的方法嗎?答案是肯定的,它就是:

4、協(xié)程,使用它之前我們先講講what/why/how(它是什么/為什么用它/怎么使用它)
?

what:

協(xié)程是一種用戶級的輕量級線程。協(xié)程擁有自己的寄存器上下文和棧。協(xié)程調(diào)度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復(fù)先前保存的寄存器上下文和棧。因此:

協(xié)程能保留上一次調(diào)用時的狀態(tài)(即所有局部狀態(tài)的一個特定組合), 每次過程重入時,就相當(dāng)于進入上一次調(diào)用的狀態(tài),換種說法:進入上一次離開時所處邏輯流的位置。

在并發(fā)編程中,協(xié)程與線程類似,每個協(xié)程表示一個執(zhí)行單元,有自己的本地數(shù)據(jù),與其它協(xié)程共享全局數(shù)據(jù)和其它資源。

why:

目前主流語言基本上都選擇了多線程作為并發(fā)設(shè)施,與線程相關(guān)的概念是搶占式多任務(wù)(Preemptive multitasking),而與協(xié)程相關(guān)的是協(xié)作式多任務(wù)。

不管是進程還是線程,每次阻塞、切換都需要陷入系統(tǒng)調(diào)用(system call),先讓CPU跑操作系統(tǒng)的調(diào)度程序,然后再由調(diào)度程序決定該跑哪一個進程(線程)。
而且由于搶占式調(diào)度執(zhí)行順序無法確定的特點,使用線程時需要非常小心地處理同步問題,而協(xié)程完全不存在這個問題(事件驅(qū)動和異步程序也有同樣的優(yōu)點)。

因為協(xié)程是用戶自己來編寫調(diào)度邏輯的,對CPU來說,協(xié)程其實是單線程,所以CPU不用去考慮怎么調(diào)度、切換上下文,這就省去了CPU的切換開銷,所以協(xié)程在一定程度上又好于多線程。

how:

python里面怎么使用協(xié)程?答案是使用gevent,使用方法:看這里

使用協(xié)程,可以不受線程開銷的限制,我嘗試過一次把20W條url放在單進程的協(xié)程里執(zhí)行,完全沒問題。

所以最推薦的方法,是多進程+協(xié)程(可以看作是每個進程里都是單線程,而這個單線程是協(xié)程化的)

多進程+協(xié)程下,避開了CPU切換的開銷,又能把多個CPU充分利用起來,這種方式對于數(shù)據(jù)量較大的爬蟲還有文件讀寫之類的效率提升是巨大的。


小例子:

?
#-*- coding=utf-8 -*-
?
import requests
?
from multiprocessing import Process
?
import gevent
?
from gevent import monkey; monkey.patch_all()
?
?
?
import sys
?
reload(sys)
?
sys.setdefaultencoding('utf8')
?
def fetch(url):
?
? ? try:
?
? ? ? ? s = requests.Session()
?
? ? ? ? r = s.get(url,timeout=1)#在這里抓取頁面
?
? ? except Exception,e:
?
? ? ? ? print e?
?
? ? return ''
?
?
?
def process_start(url_list):
?
? ? tasks = []
?
? ? for url in url_list:
?
? ? ? ? tasks.append(gevent.spawn(fetch,url))
?
? ? gevent.joinall(tasks)#使用協(xié)程來執(zhí)行
?
?
?
def task_start(filepath,flag = 100000):#每10W條url啟動一個進程
?
? ? with open(filepath,'r') as reader:#從給定的文件中讀取url
?
? ? ? ? url = reader.readline().strip()
?
? ? ? ? url_list = []#這個list用于存放協(xié)程任務(wù)
?
? ? ? ? i = 0 #計數(shù)器,記錄添加了多少個url到協(xié)程隊列
?
? ? ? ? while url!='':
?
? ? ? ? ? ? i += 1
?
? ? ? ? ? ? url_list.append(url)#每次讀取出url,將url添加到隊列
?
? ? ? ? ? ? if i == flag:#一定數(shù)量的url就啟動一個進程并執(zhí)行
?
? ? ? ? ? ? ? ? p = Process(target=process_start,args=(url_list,))
?
? ? ? ? ? ? ? ? p.start()
?
? ? ? ? ? ? ? ? url_list = [] #重置url隊列
?
? ? ? ? ? ? ? ? i = 0 #重置計數(shù)器
?
? ? ? ? ? ? url = reader.readline().strip()
?
? ? ? ? if url_list not []:#若退出循環(huán)后任務(wù)隊列里還有url剩余
?
? ? ? ? ? ? p = Process(target=process_start,args=(url_list,))#把剩余的url全都放到最后這個進程來執(zhí)行
?
? ? ? ? ? ? p.start()
?
??
?
if __name__ == '__main__':
?
? ? task_start('./testData.txt')#讀取指定文件
?
?
細心的同學(xué)會發(fā)現(xiàn):上面的例子中隱藏了一個問題:進程的數(shù)量會隨著url數(shù)量的增加而不斷增加,我們在這里不使用進程池multiprocessing.Pool來控制進程數(shù)量的原因是multiprocessing.Pool和gevent有沖突不能同時使用,但是有興趣的同學(xué)可以研究一下gevent.pool這個協(xié)程池。
?

參考:https://cloud.tencent.com/developer/news/218164 《為什么有人說 Python 多線程是雞肋?》

??????????https://www.cnblogs.com/anpengapple/p/6014480.html 《python的多線程到底有沒有用? 》

????????https://blog.csdn.net/lambert310/article/details/50605748 《談?wù)刾ython的GIL、多線程、多進程》
————————————————
?


更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論