真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網站制作重慶分公司

Python也能高并發(fā)

前言

這里先引用一下百度百科的定義.

創(chuàng)新互聯(lián)是一家專業(yè)從事成都做網站、網站制作的網絡公司。作為專業(yè)網站建設公司,創(chuàng)新互聯(lián)依托的技術實力、以及多年的網站運營經驗,為您提供專業(yè)的成都網站建設、全網營銷推廣及網站設計開發(fā)服務!

并發(fā),在操作系統(tǒng)中,是指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行

里面的一個時間段內說明非常重要,這里假設這個時間段是一秒,所以本文指的并發(fā)是指服務器在一秒中處理的請求數量,即rps,那么rps高,本文就認為高并發(fā).

啥?這不是你認為的高并發(fā), 出門左轉。

操作系統(tǒng)到底在干啥?

如果由筆者來概括,操作系統(tǒng)大概做了兩件事情,計算與IO,任何具體數學計算或者邏輯判斷,或者業(yè)務邏輯都是計算,而網絡交互,磁盤交互,人機之間的交互都是IO。

高并發(fā)的瓶頸在哪?

根據筆者經驗,大多數時候在IO上面。注意,這里說得是大多數,不是說絕對。

因為大多數時候業(yè)務本質上都是從數據庫或者其他存儲上讀取內容,然后根據一定的邏輯,將數據返回給用戶,比如大多數web內容。而大多數邏輯的交互都算不上計算量多大的邏輯,CPU的速度要遠遠高于內存IO,磁盤IO,網絡IO, 而這些IO中網絡IO最慢。

在根據上面的筆者對操作系統(tǒng)的概述,當并發(fā)高到一定的程度,根據業(yè)務的不同,比如計算密集,IO密集,或兩者皆有,因此瓶頸可能出在計算上面或者IO上面,又或兩者兼有。

而本文解決的高并發(fā),是指IO密集的高并發(fā)瓶頸,因此,計算密集的高并發(fā)并不在本文的討論范圍內。

為了使本文歧義更少,這里的IO主要指網絡IO.

Python怎么處理高并發(fā)?

使用協(xié)程, 事件循環(huán), 高效IO模型(比如多路復用,比如epoll), 三者缺一不可。

很多時候,筆者看過的文章都是說協(xié)程如何如何,最后告訴我一些協(xié)程庫或者asyncio用來說明協(xié)程的威力,最終我看懂了協(xié)程,卻還是不知道它為啥能高并發(fā),這也是筆者寫本文的目的。

但是一切還是得從生成器說起,因為asyncio或者大多數協(xié)程庫內部也是通過生成器實現(xiàn)的。

注意上面的三者缺一不可。
如果只懂其中一個,那么你懂了三分之一,以此類推,只有都會了,你才知道為啥協(xié)程能高并發(fā)。

生成器

生成器的定義很抽象,現(xiàn)在不懂沒關系,但是當你懂了之后回過頭再看,會覺得定義的沒錯,并且準確。下面是定義

摘自百度百科: 生成器是一次生成一個值的特殊類型函數??梢詫⑵湟暈榭苫謴秃瘮?。

關于生成器的內容,本文著重于生成器實現(xiàn)了哪些功能,而不是生成器的原理及內部實現(xiàn)。

yield

簡單例子如下

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

上面的例子沒有什么稀奇的不是嗎?yield像一個特殊的關鍵字,將函數變成了一個類似于迭代器的對象,可以使用for循環(huán)取值。

send, next

協(xié)程自然不會這么簡單,python協(xié)程的目標是星辰大海,從上面的例之所以get不到它的野心,是因為你沒有試過send, next兩個函數。

首先說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的操作有點像for循環(huán),每調用一次next,就會從中取出一個yield出來的值,其實還是沒啥特別的,感覺還沒有for循環(huán)好用。

不過,不知道你有沒有想過,如果你只需要一個值,你next一次就可以了,然后你可以去做其他事情,等到需要的時候才回來再次next取值。

就這一部分而言,你也許知道為啥說生成器是可以暫停的了,不過,這似乎也沒什么用,那是因為你不知到時,生成器除了可以拋出值,還能將值傳遞進去。

接下來我們看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:
            # 因為gen生成器里面只有三個yield,那么只能循環(huán)三次。
            # 第四次循環(huán)的時候,生成器會拋出StopIteration異常,并且return語句里面內容放在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差不多的功能,不過send在傳遞一個值給生成器的同時,還能獲取到生成器yield拋出的值,在上面的代碼中,send分別將None,1,2,3四個值傳遞給了生成器,之所以第一需要傳遞None給生成器,是因為規(guī)定,之所以規(guī)定,因為第一次傳遞過去的值沒有特定的變量或者說對象能接收,所以規(guī)定只能傳遞None, 如果你傳遞一個非None的值進去,會拋出一下錯誤

TypeError: can't send non-None value to a just-started generator

從上面的例子我們也發(fā)現(xiàn),生成器里面的變量a,b,c獲得了,send函數發(fā)送將來的1, 2, 3.

如果你有事件循環(huán)或者說多路復用的經驗,你也許能夠隱隱察覺到微妙的感覺。

這個微妙的感覺是,是否可以將IO操作yield出來?由事件循環(huán)調度, 如果你能get到這個微妙的感覺,那么你已經知道協(xié)程高并發(fā)的秘密了.

但是還差一點點.嗯, 還差一點點了.

yield from

下面是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函數調用的middle函數的send,但是gen_func函數卻能接收到main函數傳遞的值.有一種透傳的感覺,這就是yield from的作用, 這很關鍵。

而yield from最終傳遞出來的值是StopIteration異常,異常里面的內容是最終接收生成器(本示例是gen_func)return出來的值,所以ret獲得了gen_func函數return的4.但是ret將異常里面的值取出之后會繼續(xù)將接收到的異常往上拋,所以main函數里面需要使用try語句捕獲異常。而gen_func拋出的異常里面的值已經被middle函數接收,所以middle函數會將拋出的異常里面的值設為自身return的值,

至此生成器的全部內容講解完畢,如果,你get到了這些功能,那么你已經會使用生成器了。

小結

再次強調,本小結只是說明生成器的功能,至于具體生成器內部怎么實現(xiàn)的,你可以去看其他文章,或者閱讀源代碼.

io模型

Linux平臺一共有五大IO模型,每個模型有自己的優(yōu)點與確定。根據應用場景的不同可以使用不同的IO模型。

不過本文主要的考慮場景是高并發(fā),所以會針對高并發(fā)的場景做出評價

同步IO

Python也能高并發(fā)

同步模型自然是效率最低的模型了,每次只能處理完一個連接才能處理下一個,如果只有一個線程的話, 如果有一個連接一直占用,那么后來者只能傻傻的等了。所以不適合高并發(fā),不過最簡單,符合慣性思維。

非阻塞式IO

Python也能高并發(fā)

不會阻塞后面的代碼,但是需要不停的顯式詢問內核數據是否準備好,一般通過while循環(huán),而while循環(huán)會耗費大量的CPU。所以也不適合高并發(fā)。

多路復用

Python也能高并發(fā)

當前最流行,使用最廣泛的高并發(fā)方案。

而多路復用又有三種實現(xiàn)方式, 分別是select, poll, epoll。

select, poll, epoll

select,poll由于設計的問題,當處理連接過多會造成性能線性下降,而epoll是在前人的經驗上做過改進的解決方案。不會有此問題。

不過select, poll并不是一無是處,假設場景是連接數不多,并且每個連接非?;钴S,select,poll是要性能高于epoll的。

至于為啥,查看小結參考鏈接, 或者自行查詢資料。

但是本文講解的高并發(fā)可是指的連接數非常多的。

信號驅動式IO

Python也能高并發(fā)

很偏門的一個IO模型,不曾遇見過使用案例??茨P鸵膊灰姷帽榷嗦窂陀煤糜?。

異步非阻塞IO

Python也能高并發(fā)

用得不是很多,理論上比多路復用更快,因為少了一次調用,但是實際使用并沒有比多路復用快非常多,所以為啥不使用廣泛使用的多路復用。

小結

使用最廣泛多路復用epoll, 可以使得IO操作更有效率。但是使用上有一定的難度。

至此,如果你理解了多路復用的IO模型,那么你了解python為什么能夠通過協(xié)程實現(xiàn)高并發(fā)的三分之二了。

IO模型參考: https://www.jianshu.com/p/486b0965c296
select,poll,epoll區(qū)別參考: https://www.cnblogs.com/Anker/p/3265058.html

事件循環(huán)

上面的IO模型能夠解決IO的效率問題,但是實際使用起來需要一個事件循環(huán)驅動協(xié)程去處理IO。

簡單實現(xiàn)

下面引用官方的一個簡單例子。

import selectors
import socket

# 創(chuàng)建一個selctor對象
# 在不同的平臺會使用不同的IO模型,比如Linux使用epoll, windows使用select(不確定)
# 使用select調度IO
sel = selectors.DefaultSelector()

# 回調函數,用于接收新連接
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)

# 回調函數,用戶讀取client用戶數據
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)建一個非堵塞的socket
sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

# 一個事件循環(huán),用于IO調度
# 當IO可讀或者可寫的時候, 執(zhí)行事件所對應的回調函數
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函數對應事件循環(huán),它要做的就是一遍一遍的等待IO,然后調用事件的回調函數.

但是作為事件循環(huán)遠遠不夠,比如怎么停止,怎么在事件循環(huán)中加入其他邏輯.

小結

如果就功能而言,上面的代碼似乎已經完成了高并發(fā)的影子,但是如你所見,直接使用select的編碼難度比較大, 再者回調函數素來有"回調地獄"的惡名.

實際生活中的問題要復雜的多,作為一個調庫狂魔,怎么可能會自己去實現(xiàn)這些,所以python官方實現(xiàn)了一個跨平臺的事件循環(huán),至于IO模型具體選擇,官方會做適配處理。

不過官方實現(xiàn)是在Python3.5及以后了,3.5之前的版本只能使用第三方實現(xiàn)的高并發(fā)異步IO解決方案, 比如tornado,gevent,twisted。

至此你需要get到python高并發(fā)的必要條件了.

asyncio

在本文開頭,筆者就說過,python要完成高并發(fā)需要協(xié)程,事件循環(huán),高效IO模型.而Python自帶的asyncio模塊已經全部完成了.盡情使用吧.

下面是有引用官方的一個例子

import asyncio

# 通過async聲明一個協(xié)程
async def handle_echo(reader, writer):
    # 將需要io的函數使用await等待, 那么此函數就會停止
    # 當IO操作完成會喚醒這個協(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()
# 通過asyncio.start_server方法創(chuàng)建一個協(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()

總的來說python3.5明確了什么是協(xié)程,什么是生成器,雖然原理差不多,但是這樣會使得不會讓生成器即可以作為生成器使用(比如迭代數據)又可以作為協(xié)程。

所以引入了async,await使得協(xié)程的語義更加明確。

asyncio生態(tài)

asyncio官方只實現(xiàn)了比較底層的協(xié)議,比如TCP,UDP。所以諸如HTTP協(xié)議之類都需要借助第三方庫,比如aiohttp。

雖然異步編程的生態(tài)不夠同步編程的生態(tài)那么強大,但是如果又高并發(fā)的需求不妨試試,下面說一下比較成熟的異步庫

aiohttp

異步http client/server框架

github地址: https://github.com/aio-libs/aiohttp

sanic

速度更快的類flask web框架。

github地址:
https://github.com/channelcat/sanic

uvloop

快速,內嵌于asyncio事件循環(huán)的庫,使用cython基于libuv實現(xiàn)。

官方性能測試:
nodejs的兩倍,追平golang

github地址: https://github.com/MagicStack/uvloop

為了減少歧義,這里的性能測試應該只是網絡IO高并發(fā)方面不是說任何方面追平golang。

總結

Python之所以能夠處理網絡IO高并發(fā),是因為借助了高效的IO模型,能夠最大限度的調度IO,然后事件循環(huán)使用協(xié)程處理IO,協(xié)程遇到IO操作就將控制權拋出,那么在IO準備好之前的這段事件,事件循環(huán)就可以使用其他的協(xié)程處理其他事情,然后協(xié)程在用戶空間,并且是單線程的,所以不會像多線程,多進程那樣頻繁的上下文切換,因而能夠節(jié)省大量的不必要性能損失。

注: 不要再協(xié)程里面使用time.sleep之類的同步操作,因為協(xié)程再單線程里面,所以會使得整個線程停下來等待,也就沒有協(xié)程的優(yōu)勢了

本文主要講解Python為什么能夠處理高并發(fā),不是為了講解某個庫怎么使用,所以使用細節(jié)請查閱官方文檔或者執(zhí)行。

無論什么編程語言,高性能框架,一般由事件循環(huán) + 高性能IO模型(也許是epoll)組成。


網站欄目:Python也能高并發(fā)
當前鏈接:http://weahome.cn/article/gdodej.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部