Python关键字 asynico基本用法

 更新时间:2023年01月04日 08:43:53   作者:zh_jp  
asyncio是Python的异步编程库,用于编写并发程序,它提供了一组基于协程的工具,可以帮助你实现异步网络通信、并发计算等任务,这篇文章主要介绍了Python关键字 asynico,需要的朋友可以参考下

Python异步编程之Asyncio

1. 协程简介

1.1 协程的含义及实现方法

协程(Coroutine),也可以被称为微线程,是一种用户态内的上下文切换技术。简而言之,其实就是通过一个线程实现代码块相互切换执行。例如:

def func1():
    print(1)
    ...   # 协程介入
    print(2)
    
def func2():
    print(3)
    ...   # 协程介入
    print(4)

func1()
func2()

上述代码是普通的函数定义和执行,按流程分别执行两个函数中的代码,并先后会输出:1、2、3、4。但如果介入协程技术那么就可以实现函数见代码切换执行,最终输入:1、3、2、4

在Python中有多种方式可以实现协程,例如:

  • greenlet,是一个第三方模块,用于实现协程代码(Gevent协程就是基于greenlet实现);
  • yield,生成器,借助生成器的特点也可以实现协程代码;
  • asyncio,在Python3.4中引入的模块用于编写协程代码;
  • async & awiat,在Python3.5中引入的两个关键字,结合asyncio模块可以更方便的编写协程代码。

前两种实现方式较为老旧,所以重点关注后面的方式

标准库实现方法

asyncio是Python 3.4版本引入的标准库,直接内置了对异步IO的支持。

import asyncio

@asyncio.coroutine
def func1():
    print(1)
    yield from asyncio.sleep(2)  # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(2)

@asyncio.coroutine
def func2():
    print(3)
    yield from asyncio.sleep(2) # 遇到IO耗时操作,自动化切换到tasks中的其他任务
    print(4)

tasks = [
    asyncio.ensure_future( func1() ),
    asyncio.ensure_future( func2() )
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

关键字实现方法

async & await 关键字在Python3.5版本中正式引入,代替了asyncio.coroutine 装饰器,基于他编写的协程代码其实就是上一示例的加强版,让代码可以更加简便可读。

import asyncio

async def func1():
    print(1)
    await asyncio.sleep(2)  # 耗时操作  
    print(2)

async def func2():
    print(3)
    await asyncio.sleep(2)   # 耗时操作
    print(4)

tasks = [
    asyncio.ensure_future(func1()),
    asyncio.ensure_future(func2())
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

1.2 案例演示

例如:用代码实现下载 url_list 中的图片。

方式一:同步编程实现

# requests库仅支持同步的http网络请求
import requests

def download_image(url):
  print("开始下载:",url)
  # 发送网络请求,下载图片
  response = requests.get(url)
  # 图片保存到本地文件
  file_name = url.rsplit('_')[-1]
  with open(file_name, mode='wb') as file_object:
      file_object.write(response.content)
print("下载完成")


if __name__ == '__main__':
  url_list = [
      'https://www.1.jpg',
      'https://www.2.jpg',
      'https://www.3.jpg'
  ]
  for item in url_list:
      download_image(item)

输出:按顺序发送请求,请求一次下载一张图片,假如每次下载花费1s,完成任务需要3s 以上。

方式二:基于协程的程实现

# aiohttp 为支持异步编程的http请求库
import aiohttp
import asyncio

async def fetch(session, url):
  print("发送请求:", url)
  async with session.get(url, verify_ssl=False) as response:
      content = await response.content.read()
      file_name = url.rsplit('_')[-1]
      with open(file_name, mode='wb') as file_object:
          file_object.write(content)

async def main():
  async with aiohttp.ClientSession() as session:
      url_list = [
          'https://www.1.jpg',
          'https://www.2.jpg',
          'https://www.3.jpg'
      ]
      tasks = [asyncio.create_task(fetch(session, url)) for url in url_list]
      await asyncio.wait(tasks)


if __name__ == '__main__':
  asyncio.run(main())

输出:一次发送三个下载请求,同时下载,假如每次下载花费1s,完成任务仅需要1s 左右,第一种方法的耗时为第二种的三倍。

1.3 小结

协程可以让原来要使用异步+回调方式写的非人类代码,用看似同步的方式写出来。

2. 异步编程简介

2.1 同步和异步的区别

同步 :循序渐进执行操作、请求 异步:无需等待上一步操作、请求完成,就开始下一步(每个操作仍然有先后顺序)

目前python异步相关的主流技术是通过包含关键字async&await的async模块实现。

2.2 异步编程-事件循环

事件循环,可以把他当做是一个while循环,这个while循环在周期性的运行并执行一些任务,在特定条件下终止循环。

 # 伪代码
任务列表 = [ 任务1, 任务2, 任务3,... ]
while True:
    可执行的任务列表,已完成的任务列表 = 去任务列表中检查所有的任务,将'可执行'和'已完成'的任务返回
    for 就绪任务 in 已准备就绪的任务列表:
        执行已就绪的任务
         
    for 已完成的任务 in 已完成的任务列表:
        在任务列表中移除 已完成的任
         
    如果 任务列表 中的任务都已完成,则终止循环 

在编写程序时候可以通过如下代码来获取和创建事件循环。

# 方式一:
import asyncio
# 生成或获取一个事件循环
loop = asyncio.get_event_loop()
# 将任务添加到事件循环中
loop.run_until_complete(任务)

# 方式二(python3.7及以上版本支持):
asyncio.run( 任务 )

2.3 异步编程-快速上手

async 关键字

  • 协程函数:定义函数时候由async关键字装饰的函数 async def 函数名
  • 协程对象:执行协程函数得到的协程对象。
# 协程函数
async def func():
    pass
# 协程对象
result = func()

注意:执行协程函数只会创建协程对象,函数内部代码不会执行。如果想要运行协程函数内部代码,必须要将协程对象交给事件循环来处理。

import asyncio 
async def func():
    print("执行协程函数内部代码!")
result = func()

# 调用方法1:
# loop = asyncio.get_event_loop()
# loop.run_until_complete( result )

# 调用方法2:
asyncio.run( result )

await 关键字

await + 可等待的对象(协程对象、Future、Task对象 -> IO等待),遇到IO操作挂起当前协程(任务),等IO操作完成之后再继续往下执行。当前协程挂起时,事件循环可以去执行其他协程(任务)。

import asyncio

async def others():
    print("start")
    await asyncio.sleep(2)
    print('end')
    return '返回值'

async def func():
    print("执行协程函数内部代码")
    # await等待对象的值得到结果之后再继续向下走
    response = await others()
    print("IO请求结束,结果为:", response)

asyncio.run( func() )

Task 对象

Task对象的作用是在事件循环中添加多个任务,用于并发调度协程,通过asyncio.create_task(协程对象)的方式创建Task对象,这样可以让协程加入事件循环中等待被调度执行。

async def module_a():
    print("start module_a")
    await asyncio.sleep(2) # 模拟 module_a 的io操作
    print('end module_a')
    return 'module_a 完成'

async def module_b():
    print("start module_b")
    await asyncio.sleep(1) # 模拟 module_a 的io操作
    print('end module_b')
    return 'module_b 完成'  

task_list = [
    module_a(),
    module_b(), 
]

done,pending = asyncio.run( asyncio.wait(task_list) )
print(done)

2.4 案例演示

例如:用代码实现连接并查询数据库的同时,下载一个APK文件到本地。

import asyncio
import aiomysql
import os
import aiofiles as aiofiles
from aiohttp import ClientSession

async def get_app():

    url = "http://www.123.apk"
    async with ClientSession() as session:
        # 网络IO请求,获取响应
        async with session.get(url)as res:
            if res.status == 200:
                print("下载成功", res)
                # 磁盘IO请求,读取响应数据
                apk = await res.content.read()
                async  with  aiofiles.open("demo2.apk", "wb") as f:
                    # 磁盘IO请求,数据写入本地磁盘
                    await f.write(apk)
            else:
                print("下载失败")

async def excute_sql(sql):
    # 网络IO操作:连接MySQL
    conn = await aiomysql.connect(host='127.0.0.1', port=3306, user='root', password='123', db='mysql', )
    # 网络IO操作:创建CURSOR
    cur = await conn.cursor()
    # 网络IO操作:执行SQL
    await cur.execute(sql)
    # 网络IO操作:获取SQL结果
    result = await cur.fetchall()
    print(result)
    # 网络IO操作:关闭链接
    await cur.close()
    conn.close()

task_list = [get_app(), execute_sql(sql="SELECT Host,User FROM user")]
asyncio.run(asyncio.wait(task_list))

代码逻辑分析:

【step1】asyncio.run()创建了事件循环。wait()方法将task任务列表加入到当前的事件循环中;(注意:必须先创建事件循环,后加入任务列表,否则会报错)

【step2】事件循环监听事件状态,开始执行代码,先执行列表中的get_app()方法,当代码执行到async with session.get(url)as res:时,遇到await关键字表示有IO耗时操作,线程会将该任务挂起在后台执行,并切换到另外一个异步函数excute_sql()

【step3】当代码执行到excute_sql()的第一个IO耗时操作后,线程会重复先前的操作,将该任务挂起,去执行其他可执行代码。假如此时事件循环监听到get_app()中的第一IO耗时操作已经执行完成,那么线程会切换到该方法第一个IO操作后的代码,并按顺序执行直到遇上下一个await装饰的IO操作;假如事件循环监听到excute_sql()中的第一个IO操作先于get_app()的第一个IO操作完成,那么线程会继续执行excute_sql的后续代码;

【step4】线程会重复进行上述第3点中的步骤,直到代码全部执行完成,事件循环也会随之停止。

2.5 小结

一般来说CPU的耗时运算方式有:

计算密集型的操作:计算密集型任务的特点是要进行大量的计算、逻辑判断,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等。

IO密集型的操作:涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。

异步编程基于协程实现,如果利用协程实现计算密集型操作,因为线程在上下文之间的来回切换总会经历类似于”计算“-->”保存“-->”创建新环境“ 的一系列操作,导致系统的整体性能反而会下降。所以异步编程并不适用于计算密集型的程序。然而在IO密集型操作汇总,协程在IO等待时间就去切换执行其他任务,当IO操作结束后再自动回调,那么就会大大节省资源并提供性能。

好了,以上内容就给大家介绍到这里,下面再次补充介绍Python关键字 asynico知识。

Python关键字 asynico

同步和异步

同步和异步是指程序的执行方式。在同步执行中,程序会按顺序一个接一个地执行任务,直到当前任务完成。而在异步执行中,程序会在等待当前任务完成的同时,执行其他任务。

同步执行意味着程序会阻塞,等待任务完成,而异步执行则意味着程序不会阻塞,可以同时执行多个任务。

同步和异步的选择取决于你的程序需求。如果你的程序需要等待某些任务完成后才能继续,那么同步的方式可能是更好的选择。如果你的程序可以在等待任务完成的同时继续执行其他任务,那么异步的方式可能是更好的选择。

asyncio

asyncio是Python的异步编程库,用于编写并发程序。它提供了一组基于协程的工具,可以帮助你实现异步网络通信、并发计算等任务。

举个例子,假设你编写了一个程序,要向多个远程服务器发送请求,然后等待这些服务器的响应。如果你使用同步的方式编写程序,你可能会这样写:

import requests
def send_request(url):
    response = requests.get(url)
    return response.text
# 向服务器1发送请求
response1 = send_request("http://server1.com")

# 向服务器2发送请求
response2 = send_request("http://server2.com")

# 向服务器3发送请求
response3 = send_request("http://server3.com")

在这段代码中,你会发现,当你向服务器1发送请求时,程序会等待服务器1的响应,然后再向服务器2发送请求,最后再向服务器3发送请求。这意味着,当你发送请求时,程序都会被阻塞,直到收到响应。

如果你使用asyncio来编写程序,你可能会这样写:

import asyncio
import aiohttp
async def send_request(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()
# 向服务器1发送请求
response1 = asyncio.run(send_request("http://server1.com"))

# 向服务器2发送请求
response2 = asyncio.run(send_request("http://server2.com"))

# 向服务器3发送请求
response3 = asyncio.run(send_request("http://server3.com"))

在这段代码中,你会发现,当你向服务器1发送请求时,程序不会立刻等待服务器1的响应。相反,程序会立刻向服务器2和服务器3发送请求,然后等待所有响应的到来。这意味着,当你发送请求时,程序不会阻塞,而是会继续执行其他任务。

这就是asyncio的基本用法,它可以帮助你编写高效的异步程序。

Github主页:https://github.com/Viceversa0 发布一些感觉有用的代码。

到此这篇关于Python关键字 asynico基本用法的文章就介绍到这了,更多相关Python关键字 asynico内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

最新评论