首页 > 编程语言 >并发编程之asyncio模块

并发编程之asyncio模块

时间:2024-03-03 11:48:53浏览次数:31  
标签:异步 await 协程 函数 编程 并发 执行 asyncio

引言

Python 中的协程:

  • 协程是一种轻量级的用户级线程,它在单线程内执行不会阻塞主线程,可以在多个任务间轻松地切换,因此可以用于实现异步I/O操作。协程的实现方式与生成器非常相似,通过使用yield语句来暂停和恢复执行
  • 协程可以与asyncio库配合使用,来实现异步I/O操作。这种方式可以极大地提高程序的效率,因为程序不必等待I/O操作完成,可以在等待I/O操作期间执行其他任务。

协程框架基础

任何一个协程框架都首先必须是一个异步框架,asyncio也不例外。一个异步框架通常主要包括事件循环、事件队列、polling、timer队列,所有的异步框架皆不例外,asyncio也是如此。事件循环是实际启动之后执行的代码,事件队列用来向事件循环发送要执行的任务,polling使用multiplexing技术(如select或epoll)用来监控socket等IO活动,timer队列保存定时器,一般是个最小堆

执行过程以asyncio为例,asyncio的事件队列里面就是普通的callable,callable执行的时候调用asyncio的其他接口配置polling或者timer队列或者发送更多callable到事件队列里。polling或者timer队列会将socket活动和计时器关联到不同的回调,这个回调不是立即执行的,而是延迟回调,也就是不直接调用而是放进事件队列里,等着事件循环去调用。事件循环(也就是所谓EventLoop)开始的时候,会不断从事件队列里取callable,然后一个一个call过去,call完换下一个;事件队列里没有了,就去timer队列里取一个最近的timer作为超时时间,去调用polling,直到任意socket活动,或者超时为止,然后根据不同的条件,将socket的回调放到事件队列里,从头开始执行。

那这么简单的结构怎么实现异步编程呢?原理也很简单,每个callable都是一小份工作,当这部分工作做完之后,会等待下一个条件:如果要等socket,就设置polling回调;如果要等超时时间,就设置timer;如果要等其他callable完成,就使用同步对象的callback接口(后面会讲的Future);如果要同时等多个条件,就把所有的回调都设置上。通过这些回调不断触发,就能实现异步编程的目的。

基于回调的代码结构在这件事情上就比较直白,直接将回调函数设置成polling或者timer或者同步对象的callback就行了。

asyncio协程模块

协程的代码基本构成通常包括以下几个关键元素:

  1. 创建协程对象:首先需要创建一个协程对象,这可以通过特定的语法或库函数来实现。在Python中,可以使用async def定义一个协程函数,或者使用asyncio.create_task()创建一个协程任务。
  2. 执行协程:一旦创建了协程对象,就需要在适当的时机执行它。在Python中,可以通过await关键字来执行一个协程,将控制权交还给事件循环(event loop)。
  3. 暂停和恢复:协程的特点之一是可以在执行过程中暂停和恢复。在协程函数中,可以使用await关键字来暂停当前协程的执行,并等待其他协程或异步操作的完成。一旦等待的条件满足,协程将从暂停的地方继续执行。
  4. 异步操作:协程通常会涉及到异步操作,例如网络请求、文件读写等。这些异步操作可以使用特定的库函数或语法来完成,例如在Python中可以使用await关键字等待一个异步操作的结果。
  5. 协程的调度和并发:多个协程可以同时存在,并通过事件循环的调度来进行切换和执行。事件循环负责协程的调度和并发执行,可以根据需要进行协程的切换,从而实现并发执行的效果。

首先了解什么是协程函数

# 使用 async 声明的函数就是协程函数
async def fn():
    pass

接下来我们再来看一下什么是协程对象

  • 所谓的协程对象就是调用协程函数之后返回的对象如下代码所示:
# 使用 async 声明的函数就是协程函数
async def fn():
    pass

# 调用协程函数得到的对象就是协程对象
res = fn()
print(res) # <coroutine object fn at 0x1029684a0>
  • 注意事项:调用协程函数时,函数内部的代码不会执行,只是会返回一个协程对象!

最后了解什么是await关键字

  • await 是一个只能在协程函数中使用的关键字,用于当协程函数遇到IO操作的时候挂起当前协程(任务),
  • 当前协程挂起过程中,事件循环可以去执行其他的协程(任务)
  • 当前协程IO处理完成时,可以再次切换回来执行 await 之后的代码

下面是一个简单的Python协程函数的例子:

import asyncio

async def my_coroutine():
    print("Coroutine started")
    await asyncio.sleep(1)
    print("Coroutine resumed")

async def main():
    print("Main program started")
    task = asyncio.create_task(my_coroutine())  # 创建协程任务
    await task
    print("Main program finished")

asyncio.run(main())  # 执行主协程

在上面的例子中,my_coroutine函数是一个协程函数,其中使用了await语句来暂停和恢复协程的执行。main函数是主协程函数,用于执行协程任务并管理协程的执行顺序。通过asyncio.create_task()创建了一个协程任务,并通过await等待任务的完成。最后使用asyncio.run()来执行主协程。

这是一个简单的协程代码示例,实际的协程代码可能会更加复杂,涉及到更多的异步操作和协程的并发执行。

多任务的协程

在协程中实现多任务(即并发执行多个任务)是协程的一个重要应用场景。通过协程的暂停和恢复特性,可以在同一线程中执行多个协程任务,实现任务的协作和并发执行。

在协程中实现多任务的一种常见方式是使用事件循环(Event Loop),它负责协程的调度和执行。事件循环会不断地从可执行的协程队列中选择一个协程执行,直到所有协程完成或者被暂停。

下面是一个示例代码演示了如何使用事件循环实现多任务的协程:

import asyncio

async def task1():
    print("Task 1 started")
    await asyncio.sleep(1)
    print("Task 1 completed")

async def task2():
    print("Task 2 started")
    await asyncio.sleep(2)
    print("Task 2 completed")

async def main():
    print("Main program started")
    await asyncio.gather(task1(), task2())  # 并发执行两个任务
    print("Main program finished")

asyncio.run(main())  # 运行主协程

在上面的代码中,task1()task2()是两个协程函数,分别代表两个任务。main()是主协程函数,在其中使用asyncio.gather()函数来并发执行两个任务。await asyncio.gather(task1(), task2())等待两个任务的完成,然后继续执行后续代码。

通过事件循环(由asyncio.run(main())调用)以及asyncio.sleep()函数的使用,可以在同一线程内并发执行多个协程任务。事件循环会根据任务的状态(是否被暂停或完成)来调度协程的执行。

需要注意的是,在协程中的阻塞操作(如IO操作)应该使用异步方式完成,以避免阻塞整个事件循环。常用的IO操作库如asyncioaiohttp都提供了异步的IO操作支持。

通过使用事件循环和协程,可以方便地实现多任务的协作和并发执行,提高处理并发任务的效率和性能。

总结

协程是一种并发编程的技术,通过允许程序在执行过程中暂停和恢复,实现了高效利用资源、简化异步编程逻辑、提高代码可读性和可维护性、并发性能提升等优势。主要应用场景包括异步编程、事件驱动编程和并发任务调度等。

  • 在程序中只要看到 asyncawait 关键字,其内部就是基于协程实现的异步编程
  • 这种异步编程是通过一个线程在IO等待时间去执行其他任务,从而实现并发。

标签:异步,await,协程,函数,编程,并发,执行,asyncio
From: https://www.cnblogs.com/xiao01/p/18049750

相关文章

  • 并发编程之Gevent模块
    Gevent的介绍greenlet已经实现了协程,但是这个还要人工切换,这里介绍一个比greenlet更强大而且能够自动切换任务的第三方库,那就是gevent。gevent内部封装的greenlet,其原理是当一个greenlet遇到IO操作时,,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回......
  • 并发编程之IO模型
    引言Python的I/O模型分为同步(sync)和异步(async)两种:同步I/O模型是指,当一个线程在等待I/O操作完成时,它不能执行其他任务,需要一直等待I/O操作完成,直到接收到I/O操作的完成通知后才继续执行。异步I/O模型是指,当一个线程发起一个I/O操作后,不会等待I/O操作完成,而是直接执行其他任......
  • 并发编程补充:基于多线程实现并发的套接字通信
    服务端:fromsocketimport*fromthreadingimportThreaddefcommunicate(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 并发编程补充:基于多进程实现并发的套接字通信
    服务端:frommultiprocessingimportProcessfromsocketimport*deftalk(conn):whileTrue:try:data=conn.recv(1024)ifnotdata:breakconn.send(data.upper())exceptConnectionResetError:......
  • 并发编程之定时器
    定时器定时器,指定n秒后执行某操作简易版:fromthreadingimportTimerdeftask(name):print('hello%s'%name)t=Timer(5,task,args=('xiao',))t.start()#helloxiao应用版:##验证码定时器fromthreadingimportTimerimportrandomclassCode:......
  • 并发编程之条件Condition
    条件Condition(了解)使得线程等待,只有满足某条件时,才释放n个线程importthreadingdefrun(n):con.acquire()con.wait()print("runthethread:%s"%n)con.release()if__name__=='__main__':con=threading.Condition()foriinra......
  • 「java.util.concurrent并发包」之 Unsafe
    一unsafe介绍Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。但由于Unsafe类使Java语言拥有了类似C......
  • c++多线程编程
    c++线程库:<thread>创建线程:需要可调用的函数或者函数对象作为线程入口点例:std::threadthreadname(function_name,args...)在C++中,当使用std::thread创建线程并传递类的成员函数时,需要使用&来获取成员函数的地址,同时还需要传递对象的指针(或引用)作为第一个参数。......
  • Go语言的100个错误使用场景(55-60)|并发基础
    目录前言8.并发基础8.1混淆并发与并行的概念(#55)8.2认为并发总是更快(#56)8.3分不清何时使用互斥锁或channel(#57)8.4不理解竞态问题(#58)8.5不了解工作负载类型对并发性能的影响(#59)8.6不懂得使用Gocontexts(#60)小结前言大家好,这里是白泽。《Go语言的100个错误以及如何避免》......
  • 【STL和泛型编程】4. hashtable、unordered_set、unordered_map
    1.hashtable前置知识:【数据结构】3.跳表和散列 基本原理:将Key计算成一个数值,然后取余数得到它在表头中的位置table(篮子)里每个指针都指向一个链表(桶)来存储余数相同的值如果桶内的元素个数比篮子个数还多,则将篮子的大小扩充篮子是vector,数量是质数,初始为53,53扩充后为97......