這里先引用一下百度百科的定義.
成都創(chuàng)新互聯(lián)專注于通化網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供通化營(yíng)銷型網(wǎng)站建設(shè),通化網(wǎng)站制作、通化網(wǎng)頁(yè)設(shè)計(jì)、通化網(wǎng)站官網(wǎng)定制、小程序定制開(kāi)發(fā)服務(wù),打造通化網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供通化網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。并發(fā),在操作系統(tǒng)中,是指一個(gè)時(shí)間段中有幾個(gè)程序都處于已啟動(dòng)運(yùn)行到運(yùn)行完畢之間,且這幾個(gè)程序都是在同一個(gè)處理機(jī)上運(yùn)行,但任一個(gè)時(shí)刻點(diǎn)上只有一個(gè)程序在處理機(jī)上運(yùn)行
里面的一個(gè)時(shí)間段內(nèi)說(shuō)明非常重要,這里假設(shè)這個(gè)時(shí)間段是一秒,所以本文指的并發(fā)是指服務(wù)器在一秒中處理的請(qǐng)求數(shù)量,即rps,那么rps高,本文就認(rèn)為高并發(fā).
啥?這不是你認(rèn)為的高并發(fā), 出門(mén)左轉(zhuǎn)。
如果由筆者來(lái)概括,操作系統(tǒng)大概做了兩件事情,計(jì)算與IO,任何具體數(shù)學(xué)計(jì)算或者邏輯判斷,或者業(yè)務(wù)邏輯都是計(jì)算,而網(wǎng)絡(luò)交互,磁盤(pán)交互,人機(jī)之間的交互都是IO。
根據(jù)筆者經(jīng)驗(yàn),大多數(shù)時(shí)候在IO上面。注意,這里說(shuō)得是大多數(shù),不是說(shuō)絕對(duì)。
因?yàn)榇蠖鄶?shù)時(shí)候業(yè)務(wù)本質(zhì)上都是從數(shù)據(jù)庫(kù)或者其他存儲(chǔ)上讀取內(nèi)容,然后根據(jù)一定的邏輯,將數(shù)據(jù)返回給用戶,比如大多數(shù)web內(nèi)容。而大多數(shù)邏輯的交互都算不上計(jì)算量多大的邏輯,CPU的速度要遠(yuǎn)遠(yuǎn)高于內(nèi)存IO,磁盤(pán)IO,網(wǎng)絡(luò)IO, 而這些IO中網(wǎng)絡(luò)IO最慢。
在根據(jù)上面的筆者對(duì)操作系統(tǒng)的概述,當(dāng)并發(fā)高到一定的程度,根據(jù)業(yè)務(wù)的不同,比如計(jì)算密集,IO密集,或兩者皆有,因此瓶頸可能出在計(jì)算上面或者IO上面,又或兩者兼有。
而本文解決的高并發(fā),是指IO密集的高并發(fā)瓶頸,因此,計(jì)算密集的高并發(fā)并不在本文的討論范圍內(nèi)。
為了使本文歧義更少,這里的IO主要指網(wǎng)絡(luò)IO.
使用協(xié)程, 事件循環(huán), 高效IO模型(比如多路復(fù)用,比如epoll), 三者缺一不可。
很多時(shí)候,筆者看過(guò)的文章都是說(shuō)協(xié)程如何如何,最后告訴我一些協(xié)程庫(kù)或者asyncio用來(lái)說(shuō)明協(xié)程的威力,最終我看懂了協(xié)程,卻還是不知道它為啥能高并發(fā),這也是筆者寫(xiě)本文的目的。
但是一切還是得從生成器說(shuō)起,因?yàn)閍syncio或者大多數(shù)協(xié)程庫(kù)內(nèi)部也是通過(guò)生成器實(shí)現(xiàn)的。
注意上面的三者缺一不可。
如果只懂其中一個(gè),那么你懂了三分之一,以此類推,只有都會(huì)了,你才知道為啥協(xié)程能高并發(fā)。
生成器的定義很抽象,現(xiàn)在不懂沒(méi)關(guān)系,但是當(dāng)你懂了之后回過(guò)頭再看,會(huì)覺(jué)得定義的沒(méi)錯(cuò),并且準(zhǔn)確。下面是定義
摘自百度百科: 生成器是一次生成一個(gè)值的特殊類型函數(shù)??梢詫⑵湟暈榭苫謴?fù)函數(shù)。
關(guān)于生成器的內(nèi)容,本文著重于生成器實(shí)現(xiàn)了哪些功能,而不是生成器的原理及內(nèi)部實(shí)現(xiàn)。
簡(jiǎn)單例子如下
def gen_func():
yield 1
yield 2
yield 3
if __name__ == '__main__':
gen = gen_func()
for i in gen:
print(i)
output:
1
2
3
上面的例子沒(méi)有什么稀奇的不是嗎?yield像一個(gè)特殊的關(guān)鍵字,將函數(shù)變成了一個(gè)類似于迭代器的對(duì)象,可以使用for循環(huán)取值。
協(xié)程自然不會(huì)這么簡(jiǎn)單,python協(xié)程的目標(biāo)是星辰大海,從上面的例之所以get不到它的野心,是因?yàn)槟銢](méi)有試過(guò)send, next兩個(gè)函數(shù)。
首先說(shuō)next
def gen_func():
yield 1
yield 2
yield 3
if __name__ == '__main__':
gen = gen_func()
print(next(gen))
print(next(gen))
print(next(gen))
output:
1
2
3
next的操作有點(diǎn)像for循環(huán),每調(diào)用一次next,就會(huì)從中取出一個(gè)yield出來(lái)的值,其實(shí)還是沒(méi)啥特別的,感覺(jué)還沒(méi)有for循環(huán)好用。
不過(guò),不知道你有沒(méi)有想過(guò),如果你只需要一個(gè)值,你next一次就可以了,然后你可以去做其他事情,等到需要的時(shí)候才回來(lái)再次next取值。
就這一部分而言,你也許知道為啥說(shuō)生成器是可以暫停的了,不過(guò),這似乎也沒(méi)什么用,那是因?yàn)槟悴恢綍r(shí),生成器除了可以拋出值,還能將值傳遞進(jìn)去。
接下來(lái)我們看send的例子。
def gen_func():
a = yield 1
print("a: ", a)
b = yield 2
print("b: ", b)
c = yield 3
print("c: ", c)
return "finish"
if __name__ == '__main__':
gen = gen_func()
for i in range(4):
if i == 0:
print(gen.send(None))
else:
# 因?yàn)間en生成器里面只有三個(gè)yield,那么只能循環(huán)三次。
# 第四次循環(huán)的時(shí)候,生成器會(huì)拋出StopIteration異常,并且return語(yǔ)句里面內(nèi)容放在StopIteration異常里面
try:
print(gen.send(i))
except StopIteration as e:
print("e: ", e)
output:
1
a: 1
2
b: 2
3
c: 3
e: finish
send有著next差不多的功能,不過(guò)send在傳遞一個(gè)值給生成器的同時(shí),還能獲取到生成器yield拋出的值,在上面的代碼中,send分別將None,1,2,3四個(gè)值傳遞給了生成器,之所以第一需要傳遞None給生成器,是因?yàn)橐?guī)定,之所以規(guī)定,因?yàn)榈谝淮蝹鬟f過(guò)去的值沒(méi)有特定的變量或者說(shuō)對(duì)象能接收,所以規(guī)定只能傳遞None, 如果你傳遞一個(gè)非None的值進(jìn)去,會(huì)拋出一下錯(cuò)誤
TypeError: can't send non-None value to a just-started generator
從上面的例子我們也發(fā)現(xiàn),生成器里面的變量a,b,c獲得了,send函數(shù)發(fā)送將來(lái)的1, 2, 3.
如果你有事件循環(huán)或者說(shuō)多路復(fù)用的經(jīng)驗(yàn),你也許能夠隱隱察覺(jué)到微妙的感覺(jué)。
這個(gè)微妙的感覺(jué)是,是否可以將IO操作yield出來(lái)?由事件循環(huán)調(diào)度, 如果你能get到這個(gè)微妙的感覺(jué),那么你已經(jīng)知道協(xié)程高并發(fā)的秘密了.
但是還差一點(diǎn)點(diǎn).嗯, 還差一點(diǎn)點(diǎn)了.
下面是yield from的例子
def gen_func():
a = yield 1
print("a: ", a)
b = yield 2
print("b: ", b)
c = yield 3
print("c: ", c)
return 4
def middle():
gen = gen_func()
ret = yield from gen
print("ret: ", ret)
return "middle Exception"
def main():
mid = middle()
for i in range(4):
if i == 0:
print(mid.send(None))
else:
try:
print(mid.send(i))
except StopIteration as e:
print("e: ", e)
if __name__ == '__main__':
main()
output:
1
a: 1
2
b: 2
3
c: 3
ret: 4
e: middle Exception
從上面的代碼我們發(fā)現(xiàn),main函數(shù)調(diào)用的middle函數(shù)的send,但是gen_func函數(shù)卻能接收到main函數(shù)傳遞的值.有一種透?jìng)鞯母杏X(jué),這就是yield from的作用, 這很關(guān)鍵。
而yield from最終傳遞出來(lái)的值是StopIteration異常,異常里面的內(nèi)容是最終接收生成器(本示例是gen_func)return出來(lái)的值,所以ret獲得了gen_func函數(shù)return的4.但是ret將異常里面的值取出之后會(huì)繼續(xù)將接收到的異常往上拋,所以main函數(shù)里面需要使用try語(yǔ)句捕獲異常。而gen_func拋出的異常里面的值已經(jīng)被middle函數(shù)接收,所以middle函數(shù)會(huì)將拋出的異常里面的值設(shè)為自身return的值,
至此生成器的全部?jī)?nèi)容講解完畢,如果,你get到了這些功能,那么你已經(jīng)會(huì)使用生成器了。
再次強(qiáng)調(diào),本小結(jié)只是說(shuō)明生成器的功能,至于具體生成器內(nèi)部怎么實(shí)現(xiàn)的,你可以去看其他文章,或者閱讀源代碼.
Linux平臺(tái)一共有五大IO模型,每個(gè)模型有自己的優(yōu)點(diǎn)與確定。根據(jù)應(yīng)用場(chǎng)景的不同可以使用不同的IO模型。
不過(guò)本文主要的考慮場(chǎng)景是高并發(fā),所以會(huì)針對(duì)高并發(fā)的場(chǎng)景做出評(píng)價(jià)。
同步模型自然是效率最低的模型了,每次只能處理完一個(gè)連接才能處理下一個(gè),如果只有一個(gè)線程的話, 如果有一個(gè)連接一直占用,那么后來(lái)者只能傻傻的等了。所以不適合高并發(fā),不過(guò)最簡(jiǎn)單,符合慣性思維。
不會(huì)阻塞后面的代碼,但是需要不停的顯式詢問(wèn)內(nèi)核數(shù)據(jù)是否準(zhǔn)備好,一般通過(guò)while循環(huán),而while循環(huán)會(huì)耗費(fèi)大量的CPU。所以也不適合高并發(fā)。
當(dāng)前最流行,使用最廣泛的高并發(fā)方案。
而多路復(fù)用又有三種實(shí)現(xiàn)方式, 分別是select, poll, epoll。
select,poll由于設(shè)計(jì)的問(wèn)題,當(dāng)處理連接過(guò)多會(huì)造成性能線性下降,而epoll是在前人的經(jīng)驗(yàn)上做過(guò)改進(jìn)的解決方案。不會(huì)有此問(wèn)題。
不過(guò)select, poll并不是一無(wú)是處,假設(shè)場(chǎng)景是連接數(shù)不多,并且每個(gè)連接非?;钴S,select,poll是要性能高于epoll的。
至于為啥,查看小結(jié)參考鏈接, 或者自行查詢資料。
但是本文講解的高并發(fā)可是指的連接數(shù)非常多的。
很偏門(mén)的一個(gè)IO模型,不曾遇見(jiàn)過(guò)使用案例。看模型也不見(jiàn)得比多路復(fù)用好用。
用得不是很多,理論上比多路復(fù)用更快,因?yàn)樯倭艘淮握{(diào)用,但是實(shí)際使用并沒(méi)有比多路復(fù)用快非常多,所以為啥不使用廣泛使用的多路復(fù)用。
使用最廣泛多路復(fù)用epoll, 可以使得IO操作更有效率。但是使用上有一定的難度。
至此,如果你理解了多路復(fù)用的IO模型,那么你了解python為什么能夠通過(guò)協(xié)程實(shí)現(xiàn)高并發(fā)的三分之二了。
IO模型參考: https://www.jianshu.com/p/486b0965c296
select,poll,epoll區(qū)別參考: https://www.cnblogs.com/Anker/p/3265058.html
上面的IO模型能夠解決IO的效率問(wèn)題,但是實(shí)際使用起來(lái)需要一個(gè)事件循環(huán)驅(qū)動(dòng)協(xié)程去處理IO。
下面引用官方的一個(gè)簡(jiǎn)單例子。
import selectors
import socket
# 創(chuàng)建一個(gè)selctor對(duì)象
# 在不同的平臺(tái)會(huì)使用不同的IO模型,比如Linux使用epoll, windows使用select(不確定)
# 使用select調(diào)度IO
sel = selectors.DefaultSelector()
# 回調(diào)函數(shù),用于接收新連接
def accept(sock, mask):
conn, addr = sock.accept() # Should be ready
print('accepted', conn, 'from', addr)
conn.setblocking(False)
sel.register(conn, selectors.EVENT_READ, read)
# 回調(diào)函數(shù),用戶讀取client用戶數(shù)據(jù)
def read(conn, mask):
data = conn.recv(1000) # Should be ready
if data:
print('echoing', repr(data), 'to', conn)
conn.send(data) # Hope it won't block
else:
print('closing', conn)
sel.unregister(conn)
conn.close()
# 創(chuàng)建一個(gè)非堵塞的socket
sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
# 一個(gè)事件循環(huán),用于IO調(diào)度
# 當(dāng)IO可讀或者可寫(xiě)的時(shí)候, 執(zhí)行事件所對(duì)應(yīng)的回調(diào)函數(shù)
def loop():
while True:
events = sel.select()
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
if __name__ == '__main__':
loop()
上面代碼中l(wèi)oop函數(shù)對(duì)應(yīng)事件循環(huán),它要做的就是一遍一遍的等待IO,然后調(diào)用事件的回調(diào)函數(shù).
但是作為事件循環(huán)遠(yuǎn)遠(yuǎn)不夠,比如怎么停止,怎么在事件循環(huán)中加入其他邏輯.
如果就功能而言,上面的代碼似乎已經(jīng)完成了高并發(fā)的影子,但是如你所見(jiàn),直接使用select的編碼難度比較大, 再者回調(diào)函數(shù)素來(lái)有"回調(diào)地獄"的惡名.
實(shí)際生活中的問(wèn)題要復(fù)雜的多,作為一個(gè)調(diào)庫(kù)狂魔,怎么可能會(huì)自己去實(shí)現(xiàn)這些,所以python官方實(shí)現(xiàn)了一個(gè)跨平臺(tái)的事件循環(huán),至于IO模型具體選擇,官方會(huì)做適配處理。
不過(guò)官方實(shí)現(xiàn)是在Python3.5及以后了,3.5之前的版本只能使用第三方實(shí)現(xiàn)的高并發(fā)異步IO解決方案, 比如tornado,gevent,twisted。
至此你需要get到python高并發(fā)的必要條件了.
在本文開(kāi)頭,筆者就說(shuō)過(guò),python要完成高并發(fā)需要協(xié)程,事件循環(huán),高效IO模型.而Python自帶的asyncio模塊已經(jīng)全部完成了.盡情使用吧.
下面是有引用官方的一個(gè)例子
import asyncio
# 通過(guò)async聲明一個(gè)協(xié)程
async def handle_echo(reader, writer):
# 將需要io的函數(shù)使用await等待, 那么此函數(shù)就會(huì)停止
# 當(dāng)IO操作完成會(huì)喚醒這個(gè)協(xié)程
# 可以將await理解為yield from
data = await reader.read(100)
message = data.decode()
addr = writer.get_extra_info('peername')
print("Received %r from %r" % (message, addr))
print("Send: %r" % message)
writer.write(data)
await writer.drain()
print("Close the client socket")
writer.close()
# 創(chuàng)建事件循環(huán)
loop = asyncio.get_event_loop()
# 通過(guò)asyncio.start_server方法創(chuàng)建一個(gè)協(xié)程
coro = asyncio.start_server(handle_echo, '127.0.0.1', 8888, loop=loop)
server = loop.run_until_complete(coro)
# Serve requests until Ctrl+C is pressed
print('Serving on {}'.format(server.sockets[0].getsockname()))
try:
loop.run_forever()
except KeyboardInterrupt:
pass
# Close the server
server.close()
loop.run_until_complete(server.wait_closed())
loop.close()
總的來(lái)說(shuō)python3.5明確了什么是協(xié)程,什么是生成器,雖然原理差不多,但是這樣會(huì)使得不會(huì)讓生成器即可以作為生成器使用(比如迭代數(shù)據(jù))又可以作為協(xié)程。
所以引入了async,await使得協(xié)程的語(yǔ)義更加明確。
asyncio官方只實(shí)現(xiàn)了比較底層的協(xié)議,比如TCP,UDP。所以諸如HTTP協(xié)議之類都需要借助第三方庫(kù),比如aiohttp。
雖然異步編程的生態(tài)不夠同步編程的生態(tài)那么強(qiáng)大,但是如果又高并發(fā)的需求不妨試試,下面說(shuō)一下比較成熟的異步庫(kù)
異步http client/server框架
github地址: https://github.com/aio-libs/aiohttp
速度更快的類flask web框架。
github地址:
https://github.com/channelcat/sanic
快速,內(nèi)嵌于asyncio事件循環(huán)的庫(kù),使用cython基于libuv實(shí)現(xiàn)。
官方性能測(cè)試:
nodejs的兩倍,追平golang
github地址: https://github.com/MagicStack/uvloop
為了減少歧義,這里的性能測(cè)試應(yīng)該只是網(wǎng)絡(luò)IO高并發(fā)方面不是說(shuō)任何方面追平golang。
Python之所以能夠處理網(wǎng)絡(luò)IO高并發(fā),是因?yàn)榻柚烁咝У腎O模型,能夠大限度的調(diào)度IO,然后事件循環(huán)使用協(xié)程處理IO,協(xié)程遇到IO操作就將控制權(quán)拋出,那么在IO準(zhǔn)備好之前的這段事件,事件循環(huán)就可以使用其他的協(xié)程處理其他事情,然后協(xié)程在用戶空間,并且是單線程的,所以不會(huì)像多線程,多進(jìn)程那樣頻繁的上下文切換,因而能夠節(jié)省大量的不必要性能損失。
注: 不要再協(xié)程里面使用time.sleep之類的同步操作,因?yàn)閰f(xié)程再單線程里面,所以會(huì)使得整個(gè)線程停下來(lái)等待,也就沒(méi)有協(xié)程的優(yōu)勢(shì)了
本文主要講解Python為什么能夠處理高并發(fā),不是為了講解某個(gè)庫(kù)怎么使用,所以使用細(xì)節(jié)請(qǐng)查閱官方文檔或者執(zhí)行。
無(wú)論什么編程語(yǔ)言,高性能框架,一般由事件循環(huán) + 高性能IO模型(也許是epoll)組成。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無(wú)理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。