前言:python由于GIL(全局鎖)的存在,不能發(fā)揮多核的優(yōu)勢(shì),其性能一直飽受詬病。然而在IO密集型的網(wǎng)絡(luò)編程里,異步處理比同步處理能提升成百上千倍的效率,彌補(bǔ)了python性能方面的短板,如最新的微服務(wù)框架japronto,resquests per second可達(dá)百萬(wàn)級(jí)。
python還有一個(gè)優(yōu)勢(shì)是庫(kù)(第三方庫(kù))極為豐富,運(yùn)用十分方便。asyncio是python3.4版本引入到標(biāo)準(zhǔn)庫(kù),python2x沒(méi)有加這個(gè)庫(kù),畢竟python3x才是未來(lái)啊,哈哈!python3.5又加入了async/await特性。
在學(xué)習(xí)asyncio之前,我們先來(lái)理清楚同步/異步的概念:
同步是指完成事務(wù)的邏輯,先執(zhí)行第一個(gè)事務(wù),如果阻塞了,會(huì)一直等待,直到這個(gè)事務(wù)完成,再執(zhí)行第二個(gè)事務(wù),順序執(zhí)行。。。
異步是和同步相對(duì)的,異步是指在處理調(diào)用這個(gè)事務(wù)的之后,不會(huì)等待這個(gè)事務(wù)的處理結(jié)果,直接處理第二個(gè)事務(wù)去了,通過(guò)狀態(tài)、通知、回調(diào)來(lái)通知調(diào)用者處理結(jié)果。
一、asyncio
下面通過(guò)舉例來(lái)對(duì)比同步代碼和異步代碼編寫(xiě)方面的差異,其次看下兩者性能上的差距,我們使用sleep(1)模擬耗時(shí)1秒的io操作。
同步代碼:
1
2
3
4
5
6
7
8
9
10
11
|
import time def hello(): time.sleep( 1 ) def run(): for i in range ( 5 ): hello() print ( 'Hello World:%s' % time.time()) # 任何偉大的代碼都是從Hello World 開(kāi)始的! if __name__ = = '__main__' : run() |
輸出:(間隔差不多是1s)
Hello World:1527595175.4728756
Hello World:1527595176.473001
Hello World:1527595177.473494
Hello World:1527595178.4739306
Hello World:1527595179.474482
異步代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import time import asyncio # 定義異步函數(shù) async def hello(): asyncio.sleep( 1 ) print ( 'Hello World:%s' % time.time()) def run(): for i in range ( 5 ): loop.run_until_complete(hello()) loop = asyncio.get_event_loop() if __name__ = = '__main__' : run() |
輸出:
Hello World:1527595104.8338501
Hello World:1527595104.8338501
Hello World:1527595104.8338501
Hello World:1527595104.8338501
Hello World:1527595104.8338501
async def 用來(lái)定義異步函數(shù),其內(nèi)部有異步操作。每個(gè)線程有一個(gè)事件循環(huán),主線程調(diào)用asyncio.get_event_loop()時(shí)會(huì)創(chuàng)建事件循環(huán),你需要把異步的任務(wù)丟給這個(gè)循環(huán)的run_until_complete()方法,事件循環(huán)會(huì)安排協(xié)同程序的執(zhí)行。
二、aiohttp
如果需要并發(fā)http請(qǐng)求怎么辦呢,通常是用requests,但requests是同步的庫(kù),如果想異步的話需要引入aiohttp。這里引入一個(gè)類,from aiohttp import ClientSession,首先要建立一個(gè)session對(duì)象,然后用session對(duì)象去打開(kāi)網(wǎng)頁(yè)。session可以進(jìn)行多項(xiàng)操作,比如post, get, put, head等。
基本用法:
1
2
|
async with ClientSession() as session: async with session.get(url) as response: |
aiohttp異步實(shí)現(xiàn)的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
import asyncio from aiohttp import ClientSession tasks = [] url = "https://www.baidu.com/{}" async def hello(url): async with ClientSession() as session: async with session.get(url) as response: response = await response.read() print (response) if __name__ = = '__main__' : loop = asyncio.get_event_loop() loop.run_until_complete(hello(url)) |
首先async def 關(guān)鍵字定義了這是個(gè)異步函數(shù),await 關(guān)鍵字加在需要等待的操作前面,response.read()等待request響應(yīng),是個(gè)耗IO操作。然后使用ClientSession類發(fā)起http請(qǐng)求。
多鏈接異步訪問(wèn)
如果我們需要請(qǐng)求多個(gè)URL該怎么辦呢,同步的做法訪問(wèn)多個(gè)URL只需要加個(gè)for循環(huán)就可以了。但異步的實(shí)現(xiàn)方式并沒(méi)那么容易,在之前的基礎(chǔ)上需要將hello()包裝在asyncio的Future對(duì)象中,然后將Future對(duì)象列表作為任務(wù)傳遞給事件循環(huán)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import time import asyncio from aiohttp import ClientSession tasks = [] url = "https://www.baidu.com/{}" async def hello(url): async with ClientSession() as session: async with session.get(url) as response: response = await response.read() # print(response) print ( 'Hello World:%s' % time.time()) def run(): for i in range ( 5 ): task = asyncio.ensure_future(hello(url. format (i))) tasks.append(task) if __name__ = = '__main__' : loop = asyncio.get_event_loop() run() loop.run_until_complete(asyncio.wait(tasks)) |
輸出:
Hello World:1527754874.8915546
Hello World:1527754874.899039
Hello World:1527754874.90004
Hello World:1527754874.9095392
Hello World:1527754874.9190395
收集http響應(yīng)
好了,上面介紹了訪問(wèn)不同鏈接的異步實(shí)現(xiàn)方式,但是我們只是發(fā)出了請(qǐng)求,如果要把響應(yīng)一一收集到一個(gè)列表中,最后保存到本地或者打印出來(lái)要怎么實(shí)現(xiàn)呢,可通過(guò)asyncio.gather(*tasks)將響應(yīng)全部收集起來(lái),具體通過(guò)下面實(shí)例來(lái)演示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
import time import asyncio from aiohttp import ClientSession tasks = [] url = "https://www.baidu.com/{}" async def hello(url): async with ClientSession() as session: async with session.get(url) as response: # print(response) print ( 'Hello World:%s' % time.time()) return await response.read() def run(): for i in range ( 5 ): task = asyncio.ensure_future(hello(url. format (i))) tasks.append(task) result = loop.run_until_complete(asyncio.gather( * tasks)) print (result) if __name__ = = '__main__' : loop = asyncio.get_event_loop() run() |
輸出:
Hello World:1527765369.0785167
Hello World:1527765369.0845182
Hello World:1527765369.0910277
Hello World:1527765369.0920424
Hello World:1527765369.097017
[b'<!DOCTYPE html>\r\n<!--STATUS OK-->\r\n<html>\r\n<head>\r\n......
異常解決
假如你的并發(fā)達(dá)到1000個(gè),程序會(huì)報(bào)錯(cuò):ValueError: too many file descriptors in select()。這個(gè)報(bào)錯(cuò)的原因是因?yàn)?Python 調(diào)取的 select 對(duì)打開(kāi)的文件字符有最大長(zhǎng)度限制。這里我們有兩種方法解決這個(gè)問(wèn)題:1.我們可以需要限制并發(fā)數(shù)量。一次不要塞那么多任務(wù),或者限制最大并發(fā)數(shù)量。2.我們可以使用回調(diào)的方式。這里個(gè)人推薦限制并發(fā)數(shù)的方法,設(shè)置并發(fā)數(shù)為500或者600,處理速度更快。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
#coding:utf-8 import time,asyncio,aiohttp url = 'https://www.baidu.com/' async def hello(url,semaphore): async with semaphore: async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.read() async def run(): semaphore = asyncio.Semaphore( 500 ) # 限制并發(fā)量為500 to_get = [hello(url. format (),semaphore) for _ in range ( 1000 )] #總共1000任務(wù) await asyncio.wait(to_get) if __name__ = = '__main__' : # now=lambda :time.time() loop = asyncio.get_event_loop() loop.run_until_complete(run()) loop.close() |
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:http://www.cnblogs.com/shenh/p/9090586.html