小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

Celery如何修復(fù)Python的GIL問(wèn)題

 River_LaLaLa 2016-08-21

這篇文章闡述Celery是如何淘汰GIL和協(xié)同程序的。

最近,我重讀了Glyph寫(xiě)的Unyielding。如果你還沒(méi)有讀過(guò),那趕緊去。我將會(huì)在下文略述它的內(nèi)容,但是,原文絕對(duì)值得一讀。

近十年我都在研究Python全局解釋器鎖,即GIL。

關(guān)于GIL,真正的問(wèn)題是異步I/O--線程就是作為處理它的簡(jiǎn)潔方法推廣的。你接收到一個(gè)請(qǐng)求,你創(chuàng)建一個(gè)線程,魔法發(fā)生了。關(guān)注是分開(kāi)的而資源是共享的。

但是在Python里,你不能高效的這樣做,因?yàn)榫€程需要爭(zhēng)奪GIL,而每個(gè)解釋器只有一個(gè)GIL,無(wú)論你的機(jī)器有多少核。所以,即使你使用頂配的英特爾酷睿i7處理器,你也不會(huì)覺(jué)得使用線程使性能有很大提升。

理論上是這樣的,現(xiàn)實(shí)可能更糟糕--Python 3.1之前的GIL實(shí)際上在多核處理器上處理多線程時(shí)性能更糟糕并且可能使你的代碼變得更慢。

異步I/O是問(wèn)題嗎?

現(xiàn)代編程中我們做的大多數(shù)任務(wù)都可以歸結(jié)為I/O,或者通常這樣回答:

例如,從數(shù)據(jù)庫(kù)取數(shù)是I/O--你等待數(shù)據(jù)的時(shí)候,系統(tǒng)可以同時(shí)做其他事,比如,服務(wù)更多請(qǐng)求。

asyncio最近向Python添加的內(nèi)容是

    使用協(xié)同程序(coroutines)編寫(xiě)單線程并發(fā)代碼,通過(guò)socket和其他資源實(shí)現(xiàn)I/O復(fù)用,運(yùn)行網(wǎng)絡(luò)服務(wù)器和客戶端

這里面有一些假設(shè),我將分析一下:

  1. 人們需要單線程并發(fā)代碼

  2. 人們經(jīng)常需要I/O復(fù)用

  3. 人們使用協(xié)同程序

首先,我們真的需要單線程并發(fā)執(zhí)行代碼嗎?

在過(guò)去10年里,我從來(lái)沒(méi)有遇見(jiàn)一個(gè)人指出“這個(gè)代碼需要并發(fā)執(zhí)行但是使用單線程。

我認(rèn)為這里的意思其實(shí)是我們需要并發(fā)執(zhí)行,這是GIL最具爭(zhēng)議的地方--我們不能實(shí)現(xiàn)真正的并發(fā)。

我最近意識(shí)到我們真的不需要并發(fā)--稍后討論這點(diǎn),此前,我們來(lái)列出后面這兩條假設(shè),扔掉協(xié)同程序。

人們使用協(xié)同程序嗎?是的,但是不用在生產(chǎn)環(huán)境中。這可能有點(diǎn)武斷,但是我使用很多種語(yǔ)言編寫(xiě)并發(fā)程序,并且從來(lái)沒(méi)有遇到過(guò)像協(xié)同程序那么難讀的。

如果代碼意味著可讀,那么協(xié)同程序就意味著弄瞎你的眼睛。

另外,協(xié)作并發(fā)又叫協(xié)同程序在很久之前就被拋棄了。為什么?

因?yàn)閰f(xié)作程序的最基本假設(shè)是他們合作。搶先并發(fā)強(qiáng)制,或者至少?lài)L試強(qiáng)制,公平使用資源。

協(xié)同程序并沒(méi)有這樣幸運(yùn)--如果你的協(xié)同程序阻塞了,你必須等待。你的線程等待所有其他的協(xié)同程序。

那是協(xié)同程序在現(xiàn)實(shí)中的最大問(wèn)題。如果你的程序是一個(gè)shell腳本,用于計(jì)算斐波那契數(shù),那或許還行。但是在這種困境,服務(wù)器斷掉,連接超時(shí),我們不能閱讀讀取任何安裝的開(kāi)源庫(kù)的代碼。

回到并發(fā)執(zhí)行--我限制可以使用的線程數(shù)了嗎?不,一次也沒(méi)有。

我覺(jué)得我們既不需要協(xié)同程序,也不需要并發(fā)性。

我認(rèn)為我們需要的,并且值得花費(fèi)精力研究的是,非阻塞代碼。

阻塞代碼的問(wèn)題

代碼是阻塞的是說(shuō)代碼具有以下兩個(gè)特征之一:

  •   真得阻塞

  •   完成需要很長(zhǎng)時(shí)間

人類(lèi)是沒(méi)有耐心的,但是在等待機(jī)器完成操作時(shí)我們看起來(lái)更急不可耐。長(zhǎng)時(shí)間等待,還是避免阻塞,其實(shí)是同一個(gè)問(wèn)題。

我們不需要阻塞一些可以很快做完的事,比如,等待一些慢的操作(數(shù)據(jù)庫(kù)請(qǐng)求)完成時(shí),可以響應(yīng)用戶。

搶先并發(fā)(線程)是解決這個(gè)問(wèn)題的好方法,有一些優(yōu)點(diǎn):

  •    代碼可讀性好

  •    更好的利用資源 

對(duì)于線程可讀性--不多說(shuō)了,他們不是最簡(jiǎn)單的可以理解的,但是絕對(duì)比協(xié)同程序好。

關(guān)于資源--那真得是從“更好一點(diǎn)”到“不可置信”轉(zhuǎn)變的實(shí)現(xiàn)細(xì)節(jié)。他們中的大部分可以使用雙核機(jī)器中的兩個(gè)核。

在經(jīng)典線程編程我們可以:


如果thread_procession_data超時(shí)--我們將會(huì)得到一個(gè)錯(cuò)誤。當(dāng)?shù)诙€(gè)核可用時(shí)它會(huì)使用第二個(gè)核。漂亮。

現(xiàn)在我們也可以用Python這樣做--不完成一樣,但是接近。我們可以把執(zhí)行處理數(shù)據(jù)的代碼放進(jìn)一個(gè)進(jìn)程里而不使用thread_procession_data。我當(dāng)然是在說(shuō)超級(jí)棒的multiprocession庫(kù)。

但是,那樣真的更好嗎?

我仍然需要理解幾個(gè)概念,尤其是進(jìn)程間是如何共享資源的,那看起來(lái)不是很明顯。

有更好的方法嗎?

無(wú)鎖的勝利和Celery

作為程序員我只想要不阻塞的代碼。

我不關(guān)心是通過(guò)進(jìn)程,線程,事物內(nèi)存還是魔法實(shí)現(xiàn)的。

創(chuàng)建一個(gè)工作單元,描述它的參數(shù),比如優(yōu)先級(jí),你的工作完成了。在Python世界里有一個(gè)包可以滿足你--Celery。

Celery是一個(gè)龐大的項(xiàng)目,開(kāi)始你的第一個(gè)任務(wù)前有繁雜的配置。但是一旦它開(kāi)始工作,就變得美妙。

舉個(gè)例子,工作中我有一個(gè)系統(tǒng),在各種各樣的網(wǎng)絡(luò)入口拉取一個(gè)社會(huì)股。調(diào)用API需要時(shí)間,還需要加上網(wǎng)絡(luò)連接收發(fā)數(shù)據(jù)的時(shí)間。

例如:


面向用戶的代碼超過(guò)一秒鐘都不能被接受。然而我需要僅在用戶訪問(wèn)記錄視圖時(shí)才觸發(fā)這段代碼。我該怎么做?

有了Celery,我就可以用一個(gè)任務(wù)(task)包裹update_metrics,然后這樣做:


這里:

  •   update_metrics 是一個(gè)耗時(shí)操作,但是并沒(méi)有阻塞

  •   queue參數(shù)指定執(zhí)行任務(wù)的隊(duì)列

update_metrics耗時(shí)很長(zhǎng)--但是多虧Celery我不需要考慮那些:    

  •     由用戶動(dòng)作準(zhǔn)確觸發(fā)

  •     代碼可讀性高,并且非常明確

  •     資源可用時(shí)便會(huì)使用

最重要的是:我不必再苦惱于代碼是否在執(zhí)行I/O,我是否應(yīng)該讓出,或者它是被CPU或I/O強(qiáng)迫的。

Celery可以做的事

你的問(wèn)題有:抓取1000 URLs,然后計(jì)算用戶在表格里指定的3個(gè)詞的頻率。

通常,這很難--你需要定位將要抓取的URLs。連接可能超時(shí),你需要一直等待直到所有任務(wù)完成,并且你需要以某種方式存儲(chǔ)用戶的輸入。

不使用Celery,搞清楚哪里以及如何存儲(chǔ)數(shù)據(jù)就是一個(gè)噩夢(mèng)。使用Celery我只需要任務(wù):


這個(gè)例子的主要部分在最后幾行:

  •   chain用來(lái)在任務(wù)間建造通道,一個(gè)的輸出成為另一個(gè)的輸入

  •   chord用來(lái)將任務(wù)分組,使一個(gè)任務(wù)在其他任務(wù)都完成后才執(zhí)行

這樣寫(xiě)優(yōu)點(diǎn)有很多:

  •     你不需要了解它是如何執(zhí)行的??梢允蔷€程或進(jìn)程或協(xié)同程序。(在某種程度上,Celery支持所有類(lèi)型池)

  •     你不???

對(duì)了,這個(gè)例子里也有一些缺點(diǎn):

  •     因?yàn)镃elery任務(wù)是函數(shù),我們不得不使用scrape_url.subtask(args=(url,))語(yǔ)法,它并不易讀

  •    Celery需要明確的任務(wù)路徑,作為內(nèi)嵌函數(shù)的任務(wù),通常,task.py模塊--不能在其他任務(wù)中定義或者提交任務(wù)

  •     因?yàn)槲覀儾荒茉谝粋€(gè)任務(wù)的內(nèi)部定義或者通過(guò)調(diào)用另一個(gè)任務(wù)串聯(lián)起任務(wù),需要chord和chain這樣的對(duì)象,而這些對(duì)象使代碼變復(fù)雜

無(wú)鎖?

拋開(kāi)前面列出的問(wèn)題,對(duì)我而言,最大的問(wèn)題是細(xì)粒度控制的缺乏。隊(duì)列是一個(gè)偉大的實(shí)現(xiàn)無(wú)鎖編程的基本模型。

前面的例子假設(shè)你需要執(zhí)行一堆任務(wù)然后聚合結(jié)果--大約30行代碼的map/reduce。

但是讓我們考慮一種更加困難的情形--假設(shè)我們有一個(gè)不支持并發(fā)的任務(wù),完全是無(wú)鎖的,但是需要讀寫(xiě)而不使用阻塞。

我們應(yīng)該怎么做?

首先(這里我假設(shè)你使用Django整合)


這就是運(yùn)行一個(gè)同時(shí)處理最多一個(gè)任務(wù)的任務(wù)執(zhí)行單元(worker)所需要做的全部工作。


因此,不用做任何特別的事情,沒(méi)有鎖,沒(méi)有GIL問(wèn)題,我們可以讀寫(xiě)一個(gè)值。

當(dāng)然,這有一個(gè)主要問(wèn)題--我們不能同時(shí)讀取,即使可以。

結(jié)語(yǔ)

對(duì)我來(lái)講,這總結(jié)了整個(gè)GIL和協(xié)同程序/同步的爭(zhēng)論。我認(rèn)為Python核心的主要問(wèn)題是它很大程度上由C啟發(fā)。但是,在這里我認(rèn)為這是Python的缺陷。

而且我不知道這方面努力的原因。

有數(shù)百家公司運(yùn)行Python代碼作為連接邏輯(glue logic)--單線程同步代碼(看看Django多受歡迎)但是這些公司服務(wù)數(shù)以萬(wàn)計(jì)的用戶。

我認(rèn)為如果我們想要Python完全支持同步,這是方法。引入基于隊(duì)列的完全無(wú)鎖以及允許編程人員修改隊(duì)列。

Celery已經(jīng)實(shí)現(xiàn)了其中的大部分。為了在Python里擁有這些,我們需要擴(kuò)展解釋器來(lái)管理任務(wù)執(zhí)行單元和隊(duì)列,添加一些語(yǔ)法糖衣用來(lái)進(jìn)行內(nèi)嵌任務(wù)的定義和調(diào)用以便使用。

作為編程人員,我認(rèn)為我們從來(lái)不需要同步代碼。我從來(lái)不需要協(xié)同程序,也從來(lái)不需要多重I/O。

我需要的是高效表達(dá)想法和我想到它的方式的工具,抽象線程、拋棄同步、使用無(wú)鎖例程解決了這個(gè)問(wèn)題。


英文原文:http://blog./how-celery-fixed-pythons-gil-problem/
譯者:CupKnight


    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多