最早的軟件都是運(yùn)行在大型機(jī)上的,軟件使用者通過“啞終端”登陸到大型機(jī)上去運(yùn)行軟件。后來隨著PC機(jī)的興起,軟件開始主要運(yùn)行在桌面上,而數(shù)據(jù)庫這樣的軟件運(yùn)行在服務(wù)器端,這種Client/Server模式簡稱CS架構(gòu)。
隨著互聯(lián)網(wǎng)的興起,人們發(fā)現(xiàn),CS架構(gòu)不適合Web,最大的原因是Web應(yīng)用程序的修改和升級非常迅速,而CS架構(gòu)需要每個(gè)客戶端逐個(gè)升級桌面App,因此,Browser/Server模式開始流行,簡稱BS架構(gòu)。
在BS架構(gòu)下,客戶端只需要瀏覽器,應(yīng)用程序的邏輯和數(shù)據(jù)都存儲在服務(wù)器端。瀏覽器只需要請求服務(wù)器,獲取Web頁面,并把Web頁面展示給用戶即可。
當(dāng)然,Web頁面也具有極強(qiáng)的交互性。由于Web頁面是用HTML編寫的,而HTML具備超強(qiáng)的表現(xiàn)力,并且,服務(wù)器端升級后,客戶端無需任何部署就可以使用到新的版本,因此,BS架構(gòu)迅速流行起來。
今天,除了重量級的軟件如Office,Photoshop等,大部分軟件都以Web形式提供。比如,新浪提供的新聞、博客、微博等服務(wù),均是Web應(yīng)用。
Web應(yīng)用開發(fā)可以說是目前軟件開發(fā)中最重要的部分。Web開發(fā)也經(jīng)歷了好幾個(gè)階段:
靜態(tài)Web頁面:由文本編輯器直接編輯并生成靜態(tài)的HTML頁面,如果要修改Web頁面的內(nèi)容,就需要再次編輯HTML源文件,早期的互聯(lián)網(wǎng)Web頁面就是靜態(tài)的;
CGI:由于靜態(tài)Web頁面無法與用戶交互,比如用戶填寫了一個(gè)注冊表單,靜態(tài)Web頁面就無法處理。要處理用戶發(fā)送的動態(tài)數(shù)據(jù),出現(xiàn)了Common Gateway Interface,簡稱CGI,用C/C++編寫。
ASP/JSP/PHP:由于Web應(yīng)用特點(diǎn)是修改頻繁,用C/C++這樣的低級語言非常不適合Web開發(fā),而腳本語言由于開發(fā)效率高,與HTML結(jié)合緊密,因此,迅速取代了CGI模式。ASP是微軟推出的用VBScript腳本編程的Web開發(fā)技術(shù),而JSP用Java來編寫腳本,PHP本身則是開源的腳本語言。
MVC:為了解決直接用腳本語言嵌入HTML導(dǎo)致的可維護(hù)性差的問題,Web應(yīng)用也引入了Model-View-Controller的模式,來簡化Web開發(fā)。ASP發(fā)展為ASP.Net,JSP和PHP也有一大堆MVC框架。
目前,Web開發(fā)技術(shù)仍在快速發(fā)展中,異步開發(fā)、新的MVVM前端技術(shù)層出不窮。
Python的誕生歷史比Web還要早,由于Python是一種解釋型的腳本語言,開發(fā)效率高,所以非常適合用來做Web開發(fā)。
Python有上百種Web開發(fā)框架,有很多成熟的模板技術(shù),選擇Python開發(fā)Web應(yīng)用,不但開發(fā)效率高,而且運(yùn)行速度快。
本章我們會詳細(xì)討論P(yáng)ython Web開發(fā)技術(shù)。
如果要學(xué)習(xí)Web開發(fā),首先要對HTML、CSS和JavaScript作一定的了解。HTML定義了頁面的內(nèi)容,CSS來控制頁面元素的樣式,而JavaScript負(fù)責(zé)頁面的交互邏輯。
無論多么復(fù)雜的Web應(yīng)用程序,入口都是一個(gè)WSGI處理函數(shù)。HTTP請求的所有輸入信息都可以通過environ獲得,HTTP響應(yīng)的輸出都可以通過start_response()加上函數(shù)返回值作為Body。
復(fù)雜的Web應(yīng)用程序,光靠一個(gè)WSGI函數(shù)來處理還是太底層了,我們需要在WSGI之上再抽象出Web框架,進(jìn)一步簡化Web開發(fā)。
參考源碼
有了Web框架,我們在編寫Web應(yīng)用時(shí),注意力就從WSGI處理函數(shù)轉(zhuǎn)移到URL+對應(yīng)的處理函數(shù),這樣,編寫Web App就更加簡單了。
在編寫URL處理函數(shù)時(shí),除了配置URL外,從HTTP請求拿到用戶數(shù)據(jù)也是非常重要的。Web框架都提供了自己的API來實(shí)現(xiàn)這些功能。Flask通過request.form['name']來獲取表單的內(nèi)容。
有了MVC,我們就分離了Python代碼和HTML代碼。HTML代碼全部放到模板里,寫起來更有效率。
在IO編程一節(jié)中,我們已經(jīng)知道,CPU的速度遠(yuǎn)遠(yuǎn)快于磁盤、網(wǎng)絡(luò)等IO。在一個(gè)線程中,CPU執(zhí)行代碼的速度極快,然而,一旦遇到IO操作,如讀寫文件、發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時(shí),就需要等待IO操作完成,才能繼續(xù)進(jìn)行下一步操作。這種情況稱為同步IO。
在IO操作的過程中,當(dāng)前線程被掛起,而其他需要CPU執(zhí)行的代碼就無法被當(dāng)前線程執(zhí)行了。
因?yàn)橐粋€(gè)IO操作就阻塞了當(dāng)前線程,導(dǎo)致其他代碼無法執(zhí)行,所以我們必須使用多線程或者多進(jìn)程來并發(fā)執(zhí)行代碼,為多個(gè)用戶服務(wù)。每個(gè)用戶都會分配一個(gè)線程,如果遇到IO導(dǎo)致線程被掛起,其他用戶的線程不受影響。
多線程和多進(jìn)程的模型雖然解決了并發(fā)問題,但是系統(tǒng)不能無上限地增加線程。由于系統(tǒng)切換線程的開銷也很大,所以,一旦線程數(shù)量過多,CPU的時(shí)間就花在線程切換上了,真正運(yùn)行代碼的時(shí)間就少了,結(jié)果導(dǎo)致性能嚴(yán)重下降。
由于我們要解決的問題是CPU高速執(zhí)行能力和IO設(shè)備的龜速嚴(yán)重不匹配,多線程和多進(jìn)程只是解決這一問題的一種方法。
另一種解決IO問題的方法是異步IO。當(dāng)代碼需要執(zhí)行一個(gè)耗時(shí)的IO操作時(shí),它只發(fā)出IO指令,并不等待IO結(jié)果,然后就去執(zhí)行其他代碼了。一段時(shí)間后,當(dāng)IO返回結(jié)果時(shí),再通知CPU進(jìn)行處理。
可以想象如果按普通順序?qū)懗龅拇a實(shí)際上是沒法完成異步IO的:
do_some_code()f = open('/path/to/file', 'r')r = f.read() #<==線程停在此處等待IO操作結(jié)果#IO操作完成后線程才能繼續(xù)執(zhí)行:do_some_code(r)
所以,同步IO模型的代碼是無法實(shí)現(xiàn)異步IO模型的。
異步IO模型需要一個(gè)消息循環(huán),在消息循環(huán)中,主線程不斷地重復(fù)“讀取消息-處理消息”這一過程:
講解HTML、CSS和JavaScript就可以寫3本書,對于優(yōu)秀的Web開發(fā)人員來說,精通HTML、CSS和JavaScript是必須的,這里推薦一個(gè)在線學(xué)習(xí)網(wǎng)站w3schools:
以及一個(gè)對應(yīng)的中文版本:
當(dāng)我們用Python或者其他語言開發(fā)Web應(yīng)用時(shí),我們就是要在服務(wù)器端動態(tài)創(chuàng)建出HTML,這樣,瀏覽器就會向不同的用戶顯示出不同的Web頁面。
在學(xué)習(xí)異步IO模型前,我們先來了解協(xié)程。
協(xié)程,又稱微線程,纖程。英文名Coroutine。
協(xié)程的概念很早就提出來了,但直到最近幾年才在某些語言(如Lua)中得到廣泛應(yīng)用。
子程序,或者稱為函數(shù),在所有語言中都是層級調(diào)用,比如A調(diào)用B,B在執(zhí)行過程中又調(diào)用了C,C執(zhí)行完畢返回,B執(zhí)行完畢返回,最后是A執(zhí)行完畢。
所以子程序調(diào)用是通過棧實(shí)現(xiàn)的,一個(gè)線程就是執(zhí)行一個(gè)子程序。
子程序調(diào)用總是一個(gè)入口,一次返回,調(diào)用順序是明確的。而協(xié)程的調(diào)用和子程序不同。
協(xié)程看上去也是子程序,但執(zhí)行過程中,在子程序內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r(shí)候再返回來接著執(zhí)行。
注意,在一個(gè)子程序中中斷,去執(zhí)行其他子程序,不是函數(shù)調(diào)用,有點(diǎn)類似CPU的中斷。比如子程序A、B:
defA():print('1')? ? print('2')? ? print('3')defB():print('x')? ? print('y')? ? print('z')
假設(shè)由協(xié)程執(zhí)行,在執(zhí)行A的過程中,可以隨時(shí)中斷,去執(zhí)行B,B也可能在執(zhí)行過程中中斷再去執(zhí)行A,結(jié)果可能是:
1
2
x
y
3
z
但是在A中是沒有調(diào)用B的,所以協(xié)程的調(diào)用比函數(shù)調(diào)用理解起來要難一些。
看起來A、B的執(zhí)行有點(diǎn)像多線程,但協(xié)程的特點(diǎn)在于是一個(gè)線程執(zhí)行,那和多線程比,協(xié)程有何優(yōu)勢?
最大的優(yōu)勢就是協(xié)程極高的執(zhí)行效率。因?yàn)樽映绦蚯袚Q不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數(shù)量越多,協(xié)程的性能優(yōu)勢就越明顯。
第二大優(yōu)勢就是不需要多線程的鎖機(jī)制,因?yàn)橹挥幸粋€(gè)線程,也不存在同時(shí)寫變量沖突,在協(xié)程中控制共享資源不加鎖,只需要判斷狀態(tài)就好了,所以執(zhí)行效率比多線程高很多。
因?yàn)閰f(xié)程是一個(gè)線程執(zhí)行,那怎么利用多核CPU呢?最簡單的方法是多進(jìn)程+協(xié)程,既充分利用多核,又充分發(fā)揮協(xié)程的高效率,可獲得極高的性能。
Python對協(xié)程的支持是通過generator實(shí)現(xiàn)的。
在generator中,我們不但可以通過for循環(huán)來迭代,還可以不斷調(diào)用next()函數(shù)獲取由yield語句返回的下一個(gè)值。
但是Python的yield不但可以返回一個(gè)值,它還可以接收調(diào)用者發(fā)出的參數(shù)。
來看例子:
傳統(tǒng)的生產(chǎn)者-消費(fèi)者模型是一個(gè)線程寫消息,一個(gè)線程取消息,通過鎖機(jī)制控制隊(duì)列和等待,但一不小心就可能死鎖。
如果改用協(xié)程,生產(chǎn)者生產(chǎn)消息后,直接通過yield跳轉(zhuǎn)到消費(fèi)者開始執(zhí)行,待消費(fèi)者執(zhí)行完畢后,切換回生產(chǎn)者繼續(xù)生產(chǎn),效率極高:
defconsumer():r =''whileTrue:? ? ? ? n =yieldrifnotn:returnprint('[CONSUMER] Consuming %s...'% n)? ? ? ? r ='200 OK'defproduce(c):c.send(None)? ? n =0whilen <5:? ? ? ? n = n +1print('[PRODUCER] Producing %s...'% n)? ? ? ? r = c.send(n)? ? ? ? print('[PRODUCER] Consumer return: %s'% r)? ? c.close()c = consumer()produce(c)
執(zhí)行結(jié)果:
[PRODUCER]Producing1...[CONSUMER]Consuming1...[PRODUCER]Consumerreturn: 200OK[PRODUCER]Producing2...[CONSUMER]Consuming2...[PRODUCER]Consumerreturn: 200OK[PRODUCER]Producing3...[CONSUMER]Consuming3...[PRODUCER]Consumerreturn: 200OK[PRODUCER]Producing4...[CONSUMER]Consuming4...[PRODUCER]Consumerreturn: 200OK[PRODUCER]Producing5...[CONSUMER]Consuming5...[PRODUCER]Consumerreturn: 200OK
注意到consumer函數(shù)是一個(gè)generator,把一個(gè)consumer傳入produce后:
首先調(diào)用c.send(None)啟動生成器;
然后,一旦生產(chǎn)了東西,通過c.send(n)切換到consumer執(zhí)行;
consumer通過yield拿到消息,處理,又通過yield把結(jié)果傳回;
produce拿到consumer處理的結(jié)果,繼續(xù)生產(chǎn)下一條消息;
produce決定不生產(chǎn)了,通過c.close()關(guān)閉consumer,整個(gè)過程結(jié)束。
整個(gè)流程無鎖,由一個(gè)線程執(zhí)行,produce和consumer協(xié)作完成任務(wù),所以稱為“協(xié)程”,而非線程的搶占式多任務(wù)。
最后套用Donald Knuth的一句話總結(jié)協(xié)程的特點(diǎn):
“子程序就是協(xié)程的一種特例。”