python輪詢
❶ python中進程與線程的區別是什麼
Num01–>線程
線程是操作系統中能夠進行運算調度的最小單位。它被包含在進程之中,是進程中的實際運作單位。
一個線程指的是進程中一個單一順序的控制流。
一個進程中可以並發多條線程,每條線程並行執行不同的任務。
Num02–>進程
進程就是一個程序在一個數據集上的一次動態執行過程。
進程有以下三部分組成:
1,程序:我們編寫的程序用來描述進程要完成哪些功能以及如何完成。
2,數據集:數據集則是程序在執行過程中需要的資源,比如圖片、音視頻、文件等。
3,進程式控制制塊:進程式控制制塊是用來記錄進程的外部特徵,描述進程的執行變化過程,系統可以用它來控制和管理進程,它是系統感知進程存在的唯一標記。
Num03–>進程和線程的區別:
1、運行方式不同:
進程不能單獨執行,它只是資源的集合。
進程要操作CPU,必須要先創建一個線程。
所有在同一個進程里的線程,是同享同一塊進程所佔的內存空間。
2,關系
進程中第一個線程是主線程,主線程可以創建其他線程;其他線程也可以創建線程;線程之間是平等的。
進程有父進程和子進程,獨立的內存空間,唯一的標識符:pid。
3,速度
啟動線程比啟動進程快。
運行線程和運行進程速度上是一樣的,沒有可比性。
線程共享內存空間,進程的內存是獨立的。
4,創建
父進程生成子進程,相當於復制一份內存空間,進程之間不能直接訪問
創建新線程很簡單,創建新進程需要對父進程進行一次復制。
一個線程可以控制和操作同級線程里的其他線程,但是進程只能操作子進程。
5,交互
同一個進程里的線程之間可以直接訪問。
兩個進程想通信必須通過一個中間代理來實現。
相關推薦:《Python視頻教程》
Num04–>幾個常見的概念
1,什麼的並發和並行?
並發:微觀上CPU輪流執行,宏觀上用戶看到同時執行。因為cpu切換任務非常快。
並行:是指系統真正具有同時處理多個任務(動作)的能力。
2,同步、非同步和輪詢的區別?
同步任務:B一直等著A,等A完成之後,B再執行任務。(打電話案例)
輪詢任務:B沒有一直等待A,B過一會來問一下A,過一會問下A
非同步任務:B不需要一直等著A, B先做其他事情,等A完成後A通知B。(發簡訊案例)
Num05–>進程和線程的優缺點比較
首先,要實現多任務,通常我們會設計Master-Worker模式,Master負責分配任務,Worker負責執行任務,因此,多任務環境下,通常是一個Master,多個Worker。
如果用多進程實現Master-Worker,主進程就是Master,其他進程就是Worker。
如果用多線程實現Master-Worker,主線程就是Master,其他線程就是Worker。
多進程模式最大的優點就是穩定性高,因為一個子進程崩潰了,不會影響主進程和其他子進程。(當然主進程掛了所有進程就全掛了,但是Master進程只負責分配任務,掛掉的概率低)著名的Apache最早就是採用多進程模式。
多進程模式的缺點是創建進程的代價大,在Unix/linux系統下,用fork調用還行,在Windows下創建進程開銷巨大。另外,操作系統能同時運行的進程數也是有限的,在內存和CPU的限制下,如果有幾千個進程同時運行,操作系統連調度都會成問題。
多線程模式通常比多進程快一點,但是也快不到哪去,而且,多線程模式致命的缺點就是任何一個線程掛掉都可能直接造成整個進程崩潰,因為所有線程共享進程的內存。在Windows上,如果一個線程執行的代碼出了問題,你經常可以看到這樣的提示:「該程序執行了非法操作,即將關閉」,其實往往是某個線程出了問題,但是操作系統會強制結束整個進程。
在Windows下,多線程的效率比多進程要高,所以微軟的IIS伺服器默認採用多線程模式。由於多線程存在穩定性的問題,IIS的穩定性就不如Apache。為了緩解這個問題,IIS和Apache現在又有多進程+多線程的混合模式,真是把問題越搞越復雜。
Num06–>計算密集型任務和IO密集型任務
是否採用多任務的第二個考慮是任務的類型。我們可以把任務分為計算密集型和IO密集型。
第一種:計算密集型任務的特點是要進行大量的計算,消耗CPU資源,比如計算圓周率、對視頻進行高清解碼等等,全靠CPU的運算能力。這種計算密集型任務雖然也可以用多任務完成,但是任務越多,花在任務切換的時間就越多,CPU執行任務的效率就越低,所以,要最高效地利用CPU,計算密集型任務同時進行的數量應當等於CPU的核心數。
計算密集型任務由於主要消耗CPU資源,因此,代碼運行效率至關重要。Python這樣的腳本語言運行效率很低,完全不適合計算密集型任務。對於計算密集型任務,最好用C語言編寫。
第二種:任務的類型是IO密集型,涉及到網路、磁碟IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成(因為IO的速度遠遠低於CPU和內存的速度)。對於IO密集型任務,任務越多,CPU效率越高,但也有一個限度。常見的大部分任務都是IO密集型任務,比如Web應用。
IO密集型任務執行期間,99%的時間都花在IO上,花在CPU上的時間很少,因此,用運行速度極快的C語言替換用Python這樣運行速度極低的腳本語言,完全無法提升運行效率。對於IO密集型任務,最合適的語言就是開發效率最高(代碼量最少)的語言,腳本語言是首選,C語言最差。
相關推薦:
Python中的進程是什麼
❷ 詳解Python中的協程,為什麼說它的底層是生成器
協程又稱為是微線程,英文名是Coroutine。它和線程一樣可以調度,但是不同的是線程的啟動和調度需要通過操作系統來處理。並且線程的啟動和銷毀需要涉及一些操作系統的變數申請和銷毀處理,需要的時間比較長。而協程呢,它的調度和銷毀都是程序自己來控制的,因此它更加輕量級也更加靈活。
協程有這么多優點,自然也會有一些缺點,其中最大的缺點就是需要編程語言自己支持,否則的話需要開發者自己通過一些方法來實現協程。對於大部分語言來說,都不支持這一機制。go語言由於天然支持協程,並且支持得非常好,使得它廣受好評,短短幾年時間就迅速流行起來。
對於Python來說,本身就有著一個GIL這個巨大的先天問題。GIL是Python的全局鎖,在它的限制下一個Python進程同一時間只能同時執行一個線程,即使是在多核心的機器當中。這就大大影響了Python的性能,尤其是在CPU密集型的工作上。所以為了提升Python的性能,很多開發者想出了使用多進程+協程的方式。一開始是開發者自行實現的,後來在Python3.4的版本當中,官方也收入了這個功能,因此目前可以光明正大地說,Python是支持協程的語言了。
生成器(generator)
生成器我們也在之前的文章當中介紹過,為什麼我們介紹協程需要用到生成器呢,是因為Python的協程底層就是通過生成器來實現的。
通過生成器來實現協程的原因也很簡單,我們都知道協程需要切換掛起,而生成器當中有一個yield關鍵字,剛好可以實現這個功能。所以當初那些自己在Python當中開發協程功能的程序員都是通過生成器來實現的,我們想要理解Python當中協程的運用,就必須從最原始的生成器開始。
生成器我們很熟悉了,本質上就是帶有yield這個關鍵詞的函數。
async,await和future
從Python3.5版本開始,引入了async,await和future。我們來簡單說說它們各自的用途,其中async其實就是@asyncio.coroutine,用途是完全一樣的。同樣await代替的是yield from,意為等待另外一個協程結束。
我們用這兩個一改,上面的代碼就成了:
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
由於我們加上了await,所以每次在列印之前都會等待0.5秒。我們把await換成yield from也是一樣的,只不過用await更加直觀也更加貼合協程的含義。
Future其實可以看成是一個信號量,我們創建一個全局的future,當一個協程執行完成之後,將結果存入這個future當中。其他的協程可以await future來實現阻塞。我們來看一個例子就明白了:
future = asyncio.Future()
async def test(k):
n = 0
while n < k:
await asyncio.sleep(0.5)
print('n = {}'.format(n))
n += 1
future.set_result('success')
async def log():
result = await future
print(result)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([
log(),
test(5)
]))
loop.close()
在這個例子當中我們創建了兩個協程,第一個協程是每隔0.5秒print一個數字,在print完成之後把success寫入到future當中。第二個協程就是等待future當中的數據,之後print出來。
在loop當中我們要調度執行的不再是一個協程對象了而是兩個,所以我們用asyncio當中的wait將這兩個對象包起來。只有當wait當中的兩個對象執行結束,wait才會結束。loop等待的是wait的結束,而wait等待的是傳入其中的協程的結束,這就形成了一個依賴循環,等價於這兩個協程對象結束,loop才會結束。
總結
async並不只是可以用在函數上,事實上還有很多其他的用法,比如用在with語句上,用在for循環上等等。這些用法比較小眾,細節也很多,就不一一展開了,大家感興趣的可以自行去了解一下。
不知道大家在讀這篇文章的過程當中有沒有覺得有些費勁,如果有的話,其實是很正常的。原因也很簡單,因為Python原生是不支持協程這個概念的,所以在一開始設計的時候也沒有做這方面的准備,是後來覺得有必要才加入的。那麼作為後面加入的內容,必然會對原先的很多內容產生影響,尤其是協程藉助了之前生成器的概念來實現的,那麼必然會有很多耦合不清楚的情況。這也是這一塊的語法很亂,對初學者不友好的原因。
❸ Python面試題,線程與進程的區別,Python中如何創建多線程
進程和線程這兩個概念屬於操作系統,我們經常聽說,但是可能很少有人會細究它們的含義。對於工程師而言,兩者的定義和區別還是很有必要了解清楚的。
首先說進程,進程可以看成是 CPU執行的具體的任務 。在操作系統當中,由於CPU的運行速度非常快,要比計算機當中的其他設備要快得多。比如內存、磁碟等等,所以如果CPU一次只執行一個任務,那麼會導致CPU大量時間在等待這些設備,這樣操作效率很低。為了提升計算機的運行效率,把機器的技能盡可能壓榨出來,CPU是輪詢工作的。也就是說 它一次只執行一個任務,執行一小段碎片時間之後立即切換 ,去執行其他任務。
所以在早期的單核機器的時候,看起來電腦也是並發工作的。我們可以一邊聽歌一邊上網,也不會覺得卡頓。但實際上,這是CPU輪詢的結果。在這個例子當中,聽歌的軟體和上網的軟體對於CPU而言都是 獨立的進程 。我們可以把進程簡單地理解成運行的應用,比如在安卓手機裡面,一個app啟動的時候就會對應系統中的一個進程。當然這種說法不完全准確, 一個應用也是可以啟動多個進程的 。
進程是對應CPU而言的,線程則更多針對的是程序。即使是CPU在執行當前進程的時候,程序運行的任務其實也是有分工的。舉個例子,比如聽歌軟體當中,我們需要顯示歌詞的字幕,需要播放聲音,需要監聽用戶的行為,比如是否發生了切歌、調節音量等等。所以,我們需要 進一步拆分CPU的工作 ,讓它在執行當前進程的時候,繼續通過輪詢的方式來同時做多件事情。
進程中的任務就是線程,所以從這點上來說, 進程和線程是包含關系 。一個進程當中可以包含多個線程,對於CPU而言,不能直接執行線程,一個線程一定屬於一個進程。所以我們知道,CPU進程切換切換的是執行的應用程序或者是軟體,而進程內部的線程切換,切換的是軟體當中具體的執行任務。
關於進程和線程有一個經典的模型可以說明它們之間的關系,假設CPU是一家工廠,工廠當中有多個車間。不同的車間對應不同的生產任務,有的車間生產汽車輪胎,有的車間生產汽車骨架。但是工廠的電力是有限的,同時只能滿足一個廠房的使用。
為了讓大家的進度協調,所以工廠需要輪流提供各個車間的供電。 這里的車間對應的就是進程 。
一個車間雖然只生產一種產品,但是其中的工序卻不止一個。一個車間可能會有好幾條流水線,具體的生產任務其實是流水線完成的,每一條流水線對應一個具體執行的任務。但是同樣的, 車間同一時刻也只能執行一條流水線 ,所以我們需要車間在這些流水線之間切換供電,讓各個流水線生產進度統一。
這里車間里的 流水線自然對應的就是線程的概念 ,這個模型很好地詮釋了CPU、進程和線程之間的關系。實際的原理也的確如此,不過CPU中的情況要比現實中的車間復雜得多。因為對於進程和CPU來說,它們面臨的局面都是實時變化的。車間當中的流水線是x個,下一刻可能就成了y個。
了解完了線程和進程的概念之後,對於理解電腦的配置也有幫助。比如我們買電腦,經常會碰到一個術語,就是這個電腦的CPU是某某核某某線程的。比如我當年買的第一台筆記本是4核8線程的,這其實是在說這台電腦的CPU有 4個計算核心 ,但是使用了超線程技術,使得可以把一個物理核心模擬成兩個邏輯核心。相當於我們可以用4個核心同時執行8個線程,相當於8個核心同時執行,但其實有4個核心是模擬出來的虛擬核心。
有一個問題是 為什麼是4核8線程而不是4核8進程呢 ?因為CPU並不會直接執行進程,而是執行的是進程當中的某一個線程。就好像車間並不能直接生產零件,只有流水線才能生產零件。車間負責的更多是資源的調配,所以教科書里有一句非常經典的話來詮釋: 進程是資源分配的最小單元,線程是CPU調度的最小單元 。
啟動線程Python當中為我們提供了完善的threading庫,通過它,我們可以非常方便地創建線程來執行多線程。
首先,我們引入threading中的Thread,這是一個線程的類,我們可以通過創建一個線程的實例來執行多線程。
from threading import Thread t = Thread(target=func, name='therad', args=(x, y)) t.start()簡單解釋一下它的用法,我們傳入了三個參數,分別是 target,name和args ,從名字上我們就可以猜測出它們的含義。首先是target,它傳入的是一個方法,也就是我們希望多線程執行的方法。name是我們為這個新創建的線程起的名字,這個參數可以省略,如果省略的話,系統會為它起一個系統名。當我們執行Python的時候啟動的線程名叫MainThread,通過線程的名字我們可以做區分。args是會傳遞給target這個函數的參數。
我們來舉個經典的例子:
import time, threading # 新線程執行的代碼: def loop(n): print('thread %s is running...' % threading.current_thread().name) for i in range(n): print('thread %s >>> %s' % (threading.current_thread().name, i)) time.sleep(5) print('thread %s ended.' % threading.current_thread().name) print('thread %s is running...' % threading.current_thread().name) t = threading.Thread(target=loop, name='LoopThread', args=(10, )) t.start() print('thread %s ended.' % threading.current_thread().name)我們創建了一個非常簡單的loop函數,用來執行一個循環來列印數字,我們每次列印一個數字之後這個線程會睡眠5秒鍾,所以我們看到的結果應該是每過5秒鍾屏幕上多出一行數字。
我們在Jupyter里執行一下:
表面上看這個結果沒毛病,但是其實有一個問題,什麼問題呢? 輸出的順序不太對 ,為什麼我們在列印了第一個數字0之後,主線程就結束了呢?另外一個問題是,既然主線程已經結束了, 為什麼Python進程沒有結束 , 還在向外列印結果呢?
因為線程之間是獨立的,對於主線程而言,它在執行了t.start()之後,並 不會停留,而是會一直往下執行一直到結束 。如果我們不希望主線程在這個時候結束,而是阻塞等待子線程運行結束之後再繼續運行,我們可以在代碼當中加上t.join()這一行來實現這點。
t.start() t.join() print('thread %s ended.' % threading.current_thread().name)join操作可以讓主線程在join處掛起等待,直到子線程執行結束之後,再繼續往下執行。我們加上了join之後的運行結果是這樣的:
這個就是我們預期的樣子了,等待子線程執行結束之後再繼續。
我們再來看第二個問題,為什麼主線程結束的時候,子線程還在繼續運行,Python進程沒有退出呢?這是因為默認情況下我們創建的都是用戶級線程,對於進程而言, 會等待所有用戶級線程執行結束之後才退出 。這里就有了一個問題,那假如我們創建了一個線程嘗試從一個介面當中獲取數據,由於介面一直沒有返回,當前進程豈不是會永遠等待下去?
這顯然是不合理的,所以為了解決這個問題,我們可以把創建出來的線程設置成 守護線程 。
守護線程守護線程即daemon線程,它的英文直譯其實是後台駐留程序,所以我們也可以理解成 後台線程 ,這樣更方便理解。daemon線程和用戶線程級別不同,進程不會主動等待daemon線程的執行, 當所有用戶級線程執行結束之後即會退出。進程退出時會kill掉所有守護線程 。
我們傳入daemon=True參數來將創建出來的線程設置成後台線程:
t = threading.Thread(target=loop, name='LoopThread', args=(10, ), daemon=True)這樣我們再執行看到的結果就是這樣了:
這里有一點需要注意,如果你 在jupyter當中運行是看不到這樣的結果的 。因為jupyter自身是一個進程,對於jupyter當中的cell而言,它一直是有用戶級線程存活的,所以進程不會退出。所以想要看到這樣的效果,只能通過命令行執行Python文件。
如果我們想要等待這個子線程結束,就必須通過join方法。另外,為了預防子線程鎖死一直無法退出的情況, 我們還可以 在joih當中設置timeout ,即最長等待時間,當等待時間到達之後,將不再等待。
比如我在join當中設置的timeout等於5時,屏幕上就只會輸出5個數字。
另外,如果沒有設置成後台線程的話,設置timeout雖然也有用,但是 進程仍然會等待所有子線程結束 。所以屏幕上的輸出結果會是這樣的:
雖然主線程繼續往下執行並且結束了,但是子線程仍然一直運行,直到子線程也運行結束。
關於join設置timeout這里有一個坑,如果我們只有一個線程要等待還好,如果有多個線程,我們用一個循環將它們設置等待的話。那麼 主線程一共會等待N * timeout的時間 ,這里的N是線程的數量。因為每個線程計算是否超時的開始時間是上一個線程超時結束的時間,它會等待所有線程都超時,才會一起終止它們。
比如我這樣創建3個線程:
ths = [] for i in range(3): t = threading.Thread(target=loop, name='LoopThread' + str(i), args=(10, ), daemon=True) ths.append(t) for t in ths: t.start() for t in ths: t.join(2)最後屏幕上輸出的結果是這樣的:
所有線程都存活了6秒。
總結在今天的文章當中,我們一起簡單了解了 操作系統當中線程和進程的概念 ,以及Python當中如何創建一個線程,以及關於創建線程之後的相關使用。
多線程在許多語言當中都是至關重要的,許多場景下必定會使用到多線程。比如 web後端,比如爬蟲,再比如游戲開發 以及其他所有需要涉及開發ui界面的領域。因為凡是涉及到ui,必然會需要一個線程單獨渲染頁面,另外的線程負責准備數據和執行邏輯。因此,多線程是專業程序員繞不開的一個話題,也是一定要掌握的內容之一。
❹ Python多線程問題,怎麼解決
在python里線程出問題,可能會導致主進程崩潰。 雖然python里的線程是操作系統的真實線程。
那麼怎麼解決呢?通過我們用進程方式。子進程崩潰後,會完全的釋放所有的內存和錯誤狀態。所以進程更安全。 另外通過進程,python可以很好的繞過GIL,這個全局鎖問題。
但是進程也是有局限的。不要建立超過CPU總核數的進程,否則效率也不高。
簡單的總結一下。
當我們想實現多任務處理時,首先要想到使用multiprocessing, 但是如果覺著進程太笨重,那麼就要考慮使用線程。 如果多任務處理中需要處理的太多了,可以考慮多進程,每個進程再採用多線程。如果還處理不要,就要使用輪詢模式,比如使用poll event, twisted等方式。如果是GUI方式,則要通過事件機制,或者是消息機制處理,GUI使用單線程。
所以在python里線程不要盲目用, 也不要濫用。 但是線程不安全是事實。如果僅僅是做幾個後台任務,則可以考慮使用守護線程做。如果需要做一些危險操作,可能會崩潰的,就用子進程去做。 如果需要高度穩定性,同時並發數又不高的服務。則強烈建議用多進程的multiprocessing模塊實現。
在linux或者是unix里,進程的使用代價沒有windows高。還是可以接受的。
❺ python 定時輪詢目錄指定目錄下文件
#python簡單定時器的實現
importos
importtime
importos.path
rootdir='A'
defdoWork():
forparent,dirnames,filenamesinos.walk(rootdir):
forfilenameinfilenames:
print("filenameis:"+filename)
#這里就寫你想要對數據的操作咯
defrun(interval):
whileTrue:
try:
#
time_remaining=interval-time.time()%interval
time.sleep(time_remaining)
doWork()
exceptExceptionase:
print(e)
if__name__=="__main__":
interval=60*10
run(interval)
❻ Python進程之串列與並行
串列和並行
串列指的是任務的執行方式。串列在執行多個任務時,各個任務按順序執行,完成一個之後才能進行下一個。(早期單核CPU的情況下)
並行指的是多個任務在同一時刻可以同時執行(前提是多核CPU),不需要等待。
同步和非同步
所謂同步就是一個任務的完成需要依賴另外一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成,這是一種可靠的任務序列。要成功都成功,失敗都失敗,兩個任務的狀態可以保持一致需要等待、協調運行。
所謂非同步就是彼此獨立的,分配任務後,不需要等待該任務的執行結果,繼續做自己的事,無論被分配的任務是執行成功還是失敗都是不關心的,只要自己完成了整個任務就算完成了。至於其它任務是否真正完成無法確定,所以它是不可靠的任務序列。
相關推薦:《Python視頻教程》
小結:
1、串列和同步的區別:串列指的是在早期單核CPU時,一台電腦在同一時刻只能執行一個程序,如果想要運行另一個程序需要關閉當前程序,才能執行下一個程序,是針對多個程序來說的。同步指的是在一個程序中同一時刻只能執行一個任務。是針對一個程序中多個進程或多個線程來說的。
所以兩者有著本質上的區別。串列是針對多個程序,同步是針對一個程序內部的多個進程或多個線程的。
2、並行和非同步的區別:並行指的是多核CPU,在同一時刻可以執行多個程序。非同步指的是在同一個程序內可以執行多個進程或者多個線程。
兩者本質上的區別就是並行指的是多個程序,非同步指的是一個程序內部的多個進程和多個線程。
3、並行和並發的區別:並行和並發都是指多個程序,但不同的是並行在同一時刻可以同時執行多個任務,而並發在同一時刻只能執行一個任務,通過多道技術在空間上可以開啟多個程序,在時間上通過時間片的方式輪詢多個程序,從用戶的角度來看實現了多個程序同時執行的偽並行,從CPU的角度同一時刻它只能執行一個程序,所以說他是串列的,只不過是由於CPU切換速度太快我們無法從表面看出來而已。
並行是真正的同一時刻執行多個程序,並發是通過時間輪詢的方式實現了偽並行。
阻塞與非阻塞:
阻塞:只要是涉及到I/O操作或者網路請求的都屬於阻塞如read,recv,accept。
非阻塞:只要不涉及到I/O,網路請求的在內存中可以直接計算的就是非阻塞,例如:list.append(8),dict["a"]=1就是非阻塞。
相關推薦:
Python進程之並行與並發的區別
❼ python 定時輪詢目錄指定目錄下最新文件
你每次掃描完後移走A目錄下的所有文件就好了,這樣就能確保你每次掃描到的文件都是在十分鍾以內到達A目錄下的了
❽ 有哪些應用場景適合用python的gevent來完成
一種技術的出現必然是為了解決某種問題,gevent是為了解決什麼問題呢,設想下面這種情況。
你要做一個千人在線的Web聊天室,聊天室需要能夠實時來收發消息。但是,HTTP是無狀態的,也就是說,伺服器沒有直接把消息發給瀏覽器的能力。所以你往伺服器發送數據之後,伺服器沒法把你的消息推送給其他聊天室的人,但有若干方案可以解決這個問題。
這
里假設我們採用常見的長輪詢的方案,即客戶端請求服務端獲取最新的消息,伺服器有消息就返回數據,否則將一直保持連接直到超時。這時候,如果千人在線的
話,就需要保持1000個連接,如果連接是進程模式或者線程模式,那就要開對應個數的進程或者線程,1000個進程或者線程的切換開銷會消耗太多的資源。
你仔細分析這個聊天室的代碼執行情況,會發現這么多的進程或者線程大部分時間都是閑的,它們在等瀏覽器發消息,啥事都沒干。
針對這個問題,你可以想到,要一個進程在閑的時候去干其他的事情,等這邊消息到了再回來處理就好了。gevent把這個功能實現了,切換開銷大大降低,系統性能飆升。
總結起來就一句話,如果系統資源過的消耗在進程線程切換上面,用gevent!
❾ python2.7怎麼實現非同步
改進之前
之前,我的查詢步驟很簡單,就是:
前端提交查詢請求 --> 建立資料庫連接 --> 新建游標 --> 執行命令 --> 接受結果 --> 關閉游標、連接
這幾大步驟的順序執行。
這裡面當然問題很大:
建立資料庫連接實際上就是新建一個套接字。這是進程間通信的幾種方法里,開銷最大的了。
在「執行命令」和「接受結果」兩個步驟中,線程在阻塞在資料庫內部的運行過程中,資料庫連接和游標都處於閑置狀態。
這樣一來,每一次查詢都要順序的新建資料庫連接,都要阻塞在資料庫返回結果的過程中。當前端提交大量查詢請求時,查詢效率肯定是很低的。
第一次改進
之前的模塊里,問題最大的就是第一步——建立資料庫連接套接字了。如果能夠一次性建立連接,之後查詢能夠反復服用這個連接就好了。
所以,首先應該把資料庫查詢模塊作為一個單獨的守護進程去執行,而前端app作為主進程響應用戶的點擊操作。那麼兩條進程怎麼傳遞消息呢?翻了幾天Python文檔,終於構思出來:用隊列queue作為生產者(web前端)向消費者(資料庫後端)傳遞任務的渠道。生產者,會與SQL命令一起,同時傳遞一個管道pipe的連接對象,作為任務完成後,回傳結果的渠道。確保,任務的接收方與發送方保持一致。
作為第二個問題的解決方法,可以使用線程池來並發獲取任務隊列中的task,然後執行命令並回傳結果。
第二次改進
第一次改進的效果還是很明顯的,不用任何測試手段。直接點擊頁面鏈接,可以很直觀地感覺到反應速度有很明顯的加快。
但是對於第二個問題,使用線程池還是有些欠妥當。因為,CPython解釋器存在GIL問題,所有線程實際上都在一個解釋器進程里調度。線程稍微開多一點,解釋器進程就會頻繁的切換線程,而線程切換的開銷也不小。線程多一點,甚至會出現「抖動」問題(也就是剛剛喚醒一個線程,就進入掛起狀態,剛剛換到棧幀或內存的上下文,又被換回內存或者磁碟),效率大大降低。也就是說,線程池的並發量很有限。
試過了多進程、多線程,只能在單個線程里做文章了。
Python中的asyncio庫
Python里有大量的協程庫可以實現單線程內的並發操作,比如Twisted、Gevent等等。Python官方在3.5版本里提供了asyncio庫同樣可以實現協程並發。asyncio庫大大降低了Python中協程的實現難度,就像定義普通函數那樣就可以了,只是要在def前面多加一個async關鍵詞。async def函數中,需要阻塞在其他async def函數的位置前面可以加上await關鍵詞。
import asyncio
async def wait():
await asyncio.sleep(2)
async def execute(task):
process_task(task)
await wait()
continue_job()
async def函數的執行稍微麻煩點。需要首先獲取一個loop對象,然後由這個對象代為執行async def函數。
loop = asyncio.get_event_loop()
loop.run_until_complete(execute(task))
loop.close()
loop在執行execute(task)函數時,如果遇到await關鍵字,就會暫時掛起當前協程,轉而去執行其他阻塞在await關鍵詞的協程,從而實現協程並發。
不過需要注意的是,run_until_complete()函數本身是一個阻塞函數。也就是說,當前線程會等候一個run_until_complete()函數執行完畢之後,才會繼續執行下一部函數。所以下面這段代碼並不能並發執行。
for task in task_list:
loop.run_until_complete(task)
對與這個問題,asyncio庫也有相應的解決方案:gather函數。
loop = asyncio.get_event_loop()
tasks = [asyncio.ensure_future(execute(task))
for task in task_list]
loop.run_until_complete(asyncio.gather(*tasks))
loop.close()
當然了,async def函數的執行並不只有這兩種解決方案,還有call_soon與run_forever的配合執行等等,更多內容還請參考官方文檔。
Python下的I/O多路復用
協程,實際上,也存在上下文切換,只不過開銷很輕微。而I/O多路復用則完全不存在這個問題。
目前,Linux上比較火的I/O多路復用API要算epoll了。Tornado,就是通過調用C語言封裝的epoll庫,成功解決了C10K問題(當然還有Pypy的功勞)。
在Linux里查文檔,可以看到epoll只有三類函數,調用起來比較方便易懂。
創建epoll對象,並返回其對應的文件描述符(file descriptor)。
int epoll_create(int size);
int epoll_create1(int flags);
控制監聽事件。第一個參數epfd就對應於前面命令創建的epoll對象的文件描述符;第二個參數表示該命令要執行的動作:監聽事件的新增、修改或者刪除;第三個參數,是要監聽的文件對應的描述符;第四個,代表要監聽的事件。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
等候。這是一個阻塞函數,調用者會等候內核通知所注冊的事件被觸發。
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event *events,
int maxevents, int timeout,
const sigset_t *sigmask);
在Python的select庫里:
select.epoll()對應於第一類創建函數;
epoll.register(),epoll.unregister(),epoll.modify()均是對控制函數epoll_ctl的封裝;
epoll.poll()則是對等候函數epoll_wait的封裝。
Python里epoll相關API的最大問題應該是在epoll.poll()。相比於其所封裝的epoll_wait,用戶無法手動指定要等候的事件,也就是後者的第二個參數struct epoll_event *events。沒法實現精確控制。因此只能使用替代方案:select.select()函數。
根據Python官方文檔,select.select(rlist, wlist, xlist[, timeout])是對Unix系統中select函數的直接調用,與C語言API的傳參很接近。前三個參數都是列表,其中的元素都是要注冊到內核的文件描述符。如果想用自定義類,就要確保實現了fileno()方法。
其分別對應於:
rlist: 等候直到可讀
wlist: 等候直到可寫
xlist: 等候直到異常。這個異常的定義,要查看系統文檔。
select.select(),類似於epoll.poll(),先注冊文件和事件,然後保持等候內核通知,是阻塞函數。
實際應用
Psycopg2庫支持對非同步和協程,但和一般情況下的用法略有區別。普通資料庫連接支持不同線程中的不同游標並發查詢;而非同步連接則不支持不同游標的同時查詢。所以非同步連接的不同游標之間必須使用I/O復用方法來協調調度。
所以,我的大致實現思路是這樣的:首先並發執行大量協程,從任務隊列中提取任務,再向連接池請求連接,創建游標,然後執行命令,並返回結果。在獲取游標和接受查詢結果之前,均要阻塞等候內核通知連接可用。
其中,連接池返回連接時,會根據引用連接的協程數量,返回負載最輕的連接。這也是自己定義AsyncConnectionPool類的目的。
我的代碼位於:bottle-blog/dbservice.py
存在問題
當然了,這個流程目前還一些問題。
首先就是每次輪詢拿到任務之後,都會走這么一個流程。
獲取連接 --> 新建游標 --> 執行任務 --> 關閉游標 --> 取消連接引用
本來,最好的情況應該是:在輪詢之前,就建好游標;在輪詢時,直接等候內核通知,執行相應任務。這樣可以減少輪詢時的任務量。但是如果協程提前對應好連接,那就不能保證在獲取任務時,保持各連接負載均衡了。
所以這一塊,還有工作要做。
還有就是epoll沒能用上,有些遺憾。
以後打算寫點C語言的內容,或者用Python/C API,或者用Ctypes包裝共享庫,來實現epoll的調用。
最後,請允許我吐槽一下Python的epoll相關文檔:簡直太弱了!!!必須看源碼才能弄清楚功能。
❿ python 怎樣輪詢子進程標准輸出問題
基於文本文檔(Markdown) 設想好需要的基本需要的表、欄位、類型; 使用 Rails Migration 隨著功能的開發逐步創建表; 隨著細節功能的開發、需求,逐步增加欄位,刪除欄位,或者調整欄位類型; 第一個 Release 的時候清理 Migrations 合並成一個;...