python协程—asyncio模块

为什么使用协程?

当多线程或者多进程足够多时,实际上并不能解决性能的瓶颈问题,也就是多线程和多进程对小规模的请求可以提高效率,过多的请求实际上会降低服务资源响应效率,因此协程是更好的解决文案。

什么是协程?

当一个程序遇到阻塞时,如果将这个程序挂起,然后将它的cpu权限拿出来去执行我们的其他程序,执行完后再回过头来执行这些挂起的程序,此时所有非阻塞操作已经执行完毕,最后在一起执行阻塞程序,是不是相当于做了异步。

因此,协程的作用就是检测阻塞的程序,在单进程和单线程的情况下实现异步,相比多线程和多进程效率更高。

asyncio协程模块(python3.5以上)

协程的代码基本构成

  • 特殊函数

    • 在普通函数前添加一个async关键字,则该函数就变成一个特殊的函数
    • 特殊函数的特殊之处是什么?
      • 1.特殊函数被调用后,函数内部的程序语句(函数体)没有被立即执行
      • 2.特殊函数被调用后,会返回一个协程对象
  • 协程:

    • 调用特殊函数即创建一个协程对象。
    • 因此,协程对象 = 特殊的函数 = 函数体(一组指定形式的操作)
  • 任务:

    • 任务对象就是一个高级的协程对象,即任务对象可以绑定一个回调函数
    • 任务对象 = 协程对象 == 函数体(一组指定形式的操作)
  • 事件循环:

    • 事件循环对象,,可以将其当做是一个容器,该容器是用来装载任务对象的。创建好了一个或多个任务对象后,将任务对象装载到事件循环中,启动事件循环对象,则其内部装载的任务对象对应的相关操作就会被立即执行。
import asyncio
import time
# 创建特殊函数,一般将有阻塞操作设置特殊函数,在普通函数前加关键字async
async def get_request(url):
    print('正在请求的网址是:',url)
    time.sleep(2)
    print('请求网址结束!')
    return 123

# 自定义一个回调函数(一般来做数据解析),给任务对象使用:必须有一个参数,用来获取特殊函数的返回值
def t_callback(t):
    #参数t就是任务对象
    data = t.result() #result()函数就可以返回特殊函数内部的返回值
    print('获取到特殊函数的返回值为:',data)

# 创建协程对象
c = get_request('www.123.com')

# 创建任务对象
task = asyncio.ensure_future(c)

# 任务对象添加回调函数,事件对象启用的时候,特殊函数和回调函数按顺序执行
task.add_done_callback(t_callback)

#创建事件循环对象
loop = asyncio.get_event_loop()
loop.run_until_complete(task)

多任务的协程

特殊函数内部,不可以出现不支持异步模块的代码,否则会中断整个异步效果,例如sleep,requests,可以通过执行程序来判断。具体操作如下:

添加多个任务需要使用:wait()函数,其他代码省略,看最后一步
'''
特殊函数
回调函数
协程对象
'''

tasks = []
for url in urls:
    c = get_request(url)
    task = asyncio.ensure_future(c)
    task.add_done_callback(parse)
    tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
await关键字:挂起发生阻塞操作的任务对象。在任务对象表示的操作中,凡是阻塞操作的前面都必须加上await关键字进行修饰,但不是所有阻塞操作都可以加await,需要添加支持协程的阻塞操作,await才会生效

async def get_request(url):
    print('正在请求:',url)
    await asyncio.sleep(2)
    print('请求结束:',url)

完整代码

# 特殊函数先写出基本的网络请求框架,然后在每个with前面加async,每个阻塞操作前await,便于看懂和记忆
# 使用with是为了关闭协程,避免浪费资源
import asyncio
import time
from lxml import etree
import aiohttp
start = time.time()
urls = [
    'https://www.baidu.com',
    'https://www.baidu.com',
    'https://www.baidu.com'
]
#该任务是用来对指定url发起请求,获取响应数据
async def get_request(url):
    # requests是不支持异步的模块,所以加了await也没用
    # response = await requests.get(url=url)

    #aiohttp是支持协程的网络请求,跟requests类似,创建请求对象(aiohttp_requests)
    async with aiohttp.ClientSession() as aiohttp_requests:
        #get请求,常用参数:url,headers,params,proxy
        #post请求,常用参数:url,headers,data,proxy
        #aiohttp处理代理的参数和requests不一样(注意),此处处理代理使用proxy='http://ip:port'
        async with await aiohttp_requests.get(url=url) as response:
            page_text = await response.text()
            #text():获取字符串形式的响应数据
            #read():获取二进制形式的响应数据
            await asyncio.sleep(2)
            return page_text
def call_back(t):#回调函数专门用于数据解析
    #获取任务对象请求到的页面源码数据
    page_text = t.result()
    tree = etree.HTML(page_text)
    a = tree.xpath('//a[1]/@href')
    print(a)

tasks = []
for url in urls:
    c = get_request(url)
    task = asyncio.ensure_future(c)
    task.add_done_callback(call_back)
    tasks.append(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

print('总耗时:',time.time()-start)

uvloop加速

uvloop基于libuv,libuv是一个使用C语言实现的高性能异步I/O库,uvloop用来代替asyncio默认事件循环,可以进一步加快异步I/O操作的速度。 

import uvloop
loop = asyncio.get_event_loop()
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) # 在启用事件前加这一行代码即可
loop.run_until_complete(asyncio.wait(tasks))

你可能感兴趣的:(python三方库,python,开发语言)