python asyncio库(协程Coroutine)、I/O bound(输入/输出受限)和Network bound(网络受限)、Async/Await语法、基于epoll的实现 asyncio asynchronous I/O文章目录掌握Python中的Asyncio (Mastering Asyncio in Python)引言Introduction to AsyncioI/O bound输入/输出受限和Network bound网络受限理解Async/Await语法Understanding Async/Await Syntaxasync关键字The async Keyword示例1示例2await关键字The await Keywordasyncio跟多线程区别1. 并发模型2. 资源消耗3. 编程复杂度4. 适用场景5. GIL全局解释器锁asyncio的原理事件循环Event Loop协程Coroutine非阻塞I/OI/O多路复用任务切换异步编程术语事件循环Asyncio的核心Event Loop: Heart of Asyncio运行事件循环Running the Event Loop任务管理Task Management实用示例Practical Examples示例1并发获取多个URLExample 1: Fetching Multiple URLs Concurrently示例2异步迭代器和生成器Example 2: Asynchronous Iterators and Generators在Asyncio中处理异常Handling Exceptions in Asyncio结论Conclusion其他问题asyncio如何在执行器executor中运行同步函数解决方案掌握Python中的Asyncio (Mastering Asyncio in Python)引言Introduction to AsyncioAsynciois a Python library that provides a framework for writing concurrent同时发生的并发 code using the async/await syntax.Asyncio是一个Python库提供了一个使用async/await语法编写并发代码的框架。It’s part of the Python standard library, introduced in Python 3.4, and has since become thede facto实际上存在但可能未正式确立 solution for asynchronous programming in Python.它是Python标准库的一部分自Python 3.4引入从那时起就成为了Python中异步编程的事实上的解决方案。The library is built around the event loop, which is the core of the asyncio’s architecture, allowing developers to run multiple tasks concurrently, leading to efficient use of I/O and improved performance in applications like web servers, I/O bound and network bound applications.该库围绕事件循环构建这是asyncio架构的核心允许开发者并发运行多个任务从而在像web服务器、I/O受限和网络受限的应用程序中有效使用I/O并提高性能。I/O bound输入/输出受限和Network bound网络受限在讨论编程和系统设计时I/O bound和network bound是用来描述应用程序性能受限的两个常见术语。I/O bound输入/输出受限指的是一个程序或系统的性能主要受限于输入/输出操作的速度。这意味着CPU处理速度远快于它从磁盘、内存或其他I/O设备如键盘、显示器、网络接口等读取或写入数据的速度。在这种情况下提高性能的关键是优化I/O操作例如通过使用缓存、异步I/O操作或提高数据传输效率来减少对慢速I/O资源的依赖。Network bound网络受限则特指程序或系统的性能受限于网络延迟或带宽。这在进行大量网络通信的应用程序中尤其常见如网页服务器、数据库应用和云服务。网络受限的系统可能会因为网络拥堵、延迟高或带宽不足而导致性能下降。针对这一问题的优化措施可能包括使用更快的网络连接、优化数据传输协议或在可能的情况下减少数据传输量。在asyncio这样的异步编程库中通过使用事件循环和协程可以同时发起多个非阻塞的I/O请求使得程序在等待I/O操作完成时不会被阻塞从而能够在单个线程内并发处理多个任务。这种模式特别适合处理I/Obound和network bound的应用因为它可以显著提高资源利用率减少等待时间从而提高应用程序的整体性能。理解Async/Await语法Understanding Async/Await SyntaxBefore diving deeper into asyncio, it’s crucial to understand the async/await syntax, which is the cornerstone of asynchronous programming in Python.在深入探讨asyncio之前理解async/await语法至关重要这是Python中异步编程的基石。async关键字TheasyncKeywordTheasynckeyword is used to define a coroutine协同程序, a special type of function that can pause and resume its execution without blocking the event loop.async关键字用于定义协程一种特殊类型的函数可以在不阻塞事件循环的情况下暂停和恢复其执行。示例1下面这段代码定义一个main函数它调用fetch_data函数并在完成后打印返回的数据。最后使用asyncio.run()来运行main函数importasyncioasyncdeffetch_data():print(Fetching data...)# Simulated I/O operationawaitasyncio.sleep(1)returnData fetchedasyncdefmain():dataawaitfetch_data()print(data)# Run the main functionasyncio.run(main())这段代码演示了异步编程的基本模式定义异步函数、等待它们完成以及启动事件循环来运行这些异步任务。在这个例子中asyncio.run(main())启动了事件循环main函数在其中调用fetch_data函数并等待它完成。完成后返回的数据被打印出来。整个过程是非阻塞的这意味着程序可以在等待fetch_data完成的同时做其他事情尽管在这个简单的示例中没有体现出来。示例2为了更清楚地展示异步编程的非阻塞特性我们可以增加更多的异步任务并同时运行它们。这样可以体现出即使某些任务在等待比如等待数据从网络加载程序也能继续执行其他任务而不是无所事事地等待。下面的代码示例中我们将添加另一个异步函数fetch_more_data它也会模拟I/O操作但等待时间不同。然后我们会在main函数中同时调用fetch_data和fetch_more_data并使用asyncio.gather来并发执行这两个任务。这样可以展示程序如何同时处理多个异步操作。importasyncioasyncdeffetch_data():print(Fetching data...)awaitasyncio.sleep(1)# Simulate I/O operationreturnData fetchedasyncdeffetch_more_data():print(Fetching more data...)awaitasyncio.sleep(2)# Simulate a longer I/O operationreturnMore data fetchedasyncdefmain():# Initiate both fetch tasks concurrentlyresult1,result2awaitasyncio.gather(fetch_data(),fetch_more_data(),)# Results are readyprint(result1)print(result2)# Run the main functionasyncio.run(main())在这个示例中asyncio.gather允许你并发运行多个异步任务。它等待所有任务完成并收集它们的结果。这里的关键点是尽管fetch_data和fetch_more_data有不同的等待时间但它们会同时开始执行。这意味着总的执行时间将接近较长的那个等待时间在这个例子中是3秒而不是两个等待时间的总和因为这两个操作是并行执行的。这个模式对于提高I/O密集型应用程序如网络请求、数据库操作等的效率尤其有用因为它最大限度地减少了等待时间使得程序可以在等待操作完成的同时继续执行其他任务。await关键字TheawaitKeywordTheawaitkeyword is used to pause the execution of the enclosing coroutine until the awaited object is completed, allowing other tasks to run in the meantime.await关键字用于暂停封闭协程的执行直到等待的对象完成同时允许其他任务运行。asyncdefmain():resultawaitfetch_data()print(result)在这个例子中main()函数是一个协程因为它被async关键字定义。当在协程内部使用await关键字时实际上是在告诉Python暂停这个协程的执行等待await后面的表达式通常是另一个异步操作或协程完成。在这个等待期间Python的事件循环可以继续运行其他任务。所以在这个示例中main协程等待fetch_data()函数的结果。fetch_data()也需要是一个通过async定义的异步函数。这期间事件循环可以切换到其他任务直到fetch_data()完成并返回结果此时main协程将被唤醒并继续执行打印出结果。asyncio跟多线程区别Pythonasyncio异步I/O和多线程都可以用来处理并发任务尤其是在涉及到I/O操作时。然而它们在实现并发的方式上有根本的不同这影响了它们的使用场景、性能表现、以及编程模型。以下是asyncio与多线程处理并发任务时的一些主要区别1. 并发模型Asyncio使用单线程单进程的模型通过事件循环来管理异步任务。它利用非阻塞I/O调用来处理并发这意味着当一个操作被阻塞时asyncio会自动切换到其他任务。这种模型特别适合高I/O等待的应用如大规模网络爬虫、网络服务等。多线程则是通过创建多个并行运行的线程来实现并发每个线程可以执行不同的任务。在多线程模型中操作系统切换线程的上下文允许CPU在等待I/O操作如文件读写、网络请求等完成时执行其他任务。2. 资源消耗Asyncio由于基于单线程避免了线程切换的开销和复杂的同步机制因此通常来说资源消耗较少。这使得asyncio在处理大量的I/O密集型任务时更加高效。多线程在处理并发任务时每个线程都需要消耗系统资源如内存。当线程数量增多时线程的创建和切换会导致显著的开销特别是在高并发环境下。3. 编程复杂度Asyncio要求代码显式地编写为异步使用async和await关键字。这种编程模型虽然一开始可能需要适应但它能清晰地表达出程序的异步性质。多线程编程通常更容易上手因为它不需要改变常规的同步编程习惯。然而正确地管理线程间的同步和数据共享避免死锁、竞态条件等问题可能会让多线程编程变得复杂。4. 适用场景Asyncio适合I/O密集型任务特别是当任务可以异步执行且大部分时间花在等待I/O操作上时。多线程既适用于I/O密集型任务也适用于计算密集型任务特别是在多核CPU上因为它可以利用多核CPU的并行计算能力。5. GIL全局解释器锁在使用Python的标准实现CPython时由于GIL的存在多线程并不能在一个进程中实现真正的并行计算。GIL确保了一次只有一个线程可以执行Python字节码。因此尽管多线程适用于I/O密集型任务因为I/O操作期间解释器会释放GIL允许其他线程运行它们在CPU密集型任务上的表现并不理想。相比之下asyncio作为一种单线程模型天然就不受GIL的限制。综上所述选择asyncio还是多线程取决于具体的应用场景、性能需求、以及开发者对不同并发模型的熟悉程度。在一些场景中结合使用asyncio和多线程或多进程可能会取得最好的效果。asyncio的原理asyncio的工作原理基于事件循环Event Loop和协程Coroutine的概念。通过这两个核心概念asyncio能够在单个线程内有效地管理和调度多个异步任务从而实现非阻塞I/O操作。让我们来深入了解一下它是如何工作的事件循环Event Loop事件循环是asyncio的核心负责程序的运行以及管理所有的异步任务。它不断地循环检查和执行任务直到没有任务要运行为止。事件循环知道每个异步任务的状态以及如何切换执行的上下文。asyncio的事件循环并不是简单地通过不断遍历来检查任务状态而是更加高效地等待和响应事件。事件循环的工作原理是基于I/O多路复用技术如select、poll、epoll这些技术允许它高效地等待多个事件同时发生并在事件准备好时被唤醒处理事件而不是不断地轮询检查。事件循环的工作方式等待事件事件循环等待注册在其上的任务如I/O操作完成。这个等待过程是被动的即事件循环利用I/O多路复用技术等待操作系统的通知而不是主动查询每个任务的状态。I/O多路复用通过使用select、poll或epoll等系统调用事件循环可以同时监视多个I/O操作。这些系统调用使得事件循环在没有任务准备就绪时休眠直到至少有一个任务完成如一个网络请求响应到达或者是定时器到期。任务唤醒和调度一旦有事件发生例如一个读操作可以进行了事件循环被唤醒并调度相应的任务如继续执行暂停的协程。遍历频率和CPU使用由于asyncio事件循环的工作原理是基于等待操作系统事件通知而不是不断轮询所以它并不会导致不必要的CPU使用。实际上在等待I/O事件的时候事件循环会处于休眠状态这意味着CPU可以被其他进程或线程使用。总结asyncio的事件循环是设计用来高效地处理异步I/O操作它通过I/O多路复用技术最小化CPU使用而不是通过高频率的遍历。这种方法确保了在保持高性能的同时不会因为事件循环本身而消耗大量的CPU资源。这也是为什么asyncio特别适合于I/O密集型应用的原因之一。协程Coroutine协程是通过async定义的函数它使用await关键字暂停和恢复执行。当协程遇到await时它会暂停返回控制权给事件循环这样事件循环就可以运行其他任务。与此同时协程会等待await后面的操作完成比如I/O操作。非阻塞I/O当一个协程执行到一个I/O操作例如网络请求、数据库查询等时asyncio利用非阻塞I/O来避免阻塞整个程序。这是通过在操作系统层面使用非阻塞I/O调用实现的。这样当一个操作被阻塞等待I/O时事件循环可以继续运行其他任务。I/O多路复用asyncio使用I/O多路复用比如select、poll、epoll来同时监控多个I/O操作。这允许事件循环知道何时某个I/O操作已经准备好继续进行而无需不断轮询检查状态。任务切换当某个I/O操作完成并准备好继续执行时相关的协程会被事件循环重新唤醒。事件循环将控制权交回协程协程从上次await的地方继续执行。值得注意的是这一切都在单个线程内发生没有真正的并行执行但通过高效的调度asyncio能够模拟出并发的效果。asyncio通过事件循环和协程的机制使得Python程序能够执行非阻塞I/O操作并在单个线程内管理多个并发的异步任务。这种模型非常适合I/O密集型应用如网络服务和数据处理程序因为它允许程序在等待I/O操作时不被阻塞从而提高了整体的性能和响应速度。异步编程术语在Python的异步编程中主要涉及以下几个核心概念协程Coroutine通过async def定义的异步函数是执行异步操作的基本单位。任务Task协程的一个实例当协程被事件循环调度执行时它会被封装成任务。事件循环Event Loop程序的核心循环负责调度和执行协程以及处理所有的事件。异步编程的目的是允许协程在等待I/O操作或其他长时间运行的操作完成时暂停执行从而释放事件循环来处理其他任务。这种机制通过await关键字实现它标志着协程的执行将在等待某个异步操作完成时被暂停。事件循环Asyncio的核心Event Loop: Heart of AsyncioThe event loop is where the magic happens in asyncio. It’s responsible for managing and executing asynchronous tasks, callbacks, and events.事件循环是asyncio中发生魔法的地方。它负责管理和执行异步任务、回调和事件。运行事件循环Running the Event Loopimportasyncioasyncdefmain():# Your async code herepass# Running the event loopasyncio.run(main())任务管理Task ManagementTasks are used to schedule调度 coroutines协程 concurrently并发地. When a coroutine is wrapped into a Task with functions likeasyncio.create_task(), it’s then managed by the event loop.任务用于并发调度协程。当一个协程被像asyncio.create_task()这样的函数包装成一个任务时它就由事件循环管理了。importasyncioasyncdefperform_task1():print(Task1 start)awaitasyncio.sleep(1)print(Task2 completed)asyncdefperform_task2():print(Task2 start)awaitasyncio.sleep(2)print(Task2 completed)asyncdefmain():task1asyncio.create_task(perform_task1())task2asyncio.create_task(perform_task2())awaittask1awaittask2 asyncio.run(main())实用示例Practical Examples示例1并发获取多个URLExample 1: Fetching Multiple URLs ConcurrentlyOne of the most common use cases for asyncio is to perform concurrent network requests. This example demonstrates how to fetch multiple URLs usingaiohttp, an asynchronous HTTP Client/Server for asyncio and Python.asyncio最常见的用例之一是执行并发网络请求。这个示例展示了如何使用aiohttp并发获取多个URLaiohttp是为asyncio和Python设计的异步HTTP客户端/服务器。# -*- coding: utf-8 -*-importaiohttpimportasyncioasyncdeffetch_url(session,url):asyncwithsession.get(url)asresponse:returnawaitresponse.text()asyncdefmain(urls_):asyncwithaiohttp.ClientSession()assession:tasks[fetch_url(session,url)forurlinurls_]results_awaitasyncio.gather(*tasks)returnresults_# 更新了测试链接urls[http://example.com,https://httpbin.org/get,https://api.github.com]resultsasyncio.run(main(urls))forresultinresults:print()print(result[:100])# Print first 100 characters of each resultprint(\n)示例2异步迭代器和生成器Example 2: Asynchronous Iterators and GeneratorsAsyncio supports asynchronous iteration and asynchronous generators, making it easier to work with asynchronous code in a loop.Asyncio支持异步迭代和异步生成器使得在循环中处理异步代码变得更加容易。importasyncioclassAsyncIterable:def__init__(self,items):self.itemsitemsasyncdef__aiter__(self):foriteminself.items:# Corrected from this.items to self.items# Simulate an async operationawaitasyncio.sleep(1)yielditemasyncdefmain():asyncforiteminAsyncIterable([1,2,3]):print(item)asyncio.run(main())在Asyncio中处理异常Handling Exceptions in AsyncioException handling in asyncio is similar to synchronous code. However, when working with tasks, it’s important to retrieve the result of the task to raise any exceptions it might have encountered.在asyncio中处理异常与同步代码类似。然而在处理任务时获取任务的结果以引发可能遇到的任何异常是很重要的。importasyncioasyncdefmay_fail():awaitasyncio.sleep(1)raiseException(Something went wrong!)asyncdefmain():taskasyncio.create_task(may_fail())try:awaittaskexceptExceptionase:print(fCaught an exception:{e})asyncio.run(main())结论ConclusionAsyncio is a powerful library for asynchronous programming in Python, providing tools and constructs for writing concurrent code that is efficient, readable, and scalable.Asyncio是一个强大的Python异步编程库提供了编写高效、可读、可扩展的并发代码的工具和构造。By understanding the async/await syntax, the event loop, and how to manage tasks and exceptions, developers can harness the full potential of asyncio for building high-performance, I/O-bound applications.通过理解async/await语法、事件循环以及如何管理任务和异常开发者可以充分利用asyncio的全部潜力构建高性能的、I/O受限的应用程序。This introduction to asyncio only scratches the surface. For more in-depth knowledge, explore the official Python documentation and experiment with the library to fully grasp its capabilities and apply them to real-world projects.这篇关于asyncio的介绍只是触及了表面。为了获得更深入的知识探索官方Python文档并实验该库以充分掌握其能力并将其应用于实际项目。其他问题asyncio如何在执行器executor中运行同步函数如果fetch_data()没有使用async关键字定义即它不是一个异步函数协程那么在尝试使用await fetch_data()时你会遇到运行时错误。这是因为await关键字只能用于等待一个awaitable对象而一个普通的函数并不是awaitable的。具体来说如果你尝试这样做asyncdefmain():resultawaitfetch_data()print(result)并且fetch_data定义如下deffetch_data():# 假设这里有一些操作returnData fetched你会收到类似下面的错误消息TypeError:objectfunction cant be used in await expression这个错误告诉你尝试在await表达式中使用的对象在这个例子中是由fetch_data()调用返回的对象不支持等待。这是因为await需要其后跟的是一个异步操作这样事件循环才能在该操作完成时恢复协程的执行。普通函数同步执行并直接返回结果不涉及异步等待或事件循环。解决方案如果你需要在异步函数中调用一个同步函数并且想要保持异步执行的特性你有几个选项将同步函数改写为异步函数如果可能最直接的方式是将该同步函数改写为异步函数使用async def定义并在需要进行I/O操作等耗时操作时使用await。在执行器executor中运行同步函数asyncio允许你在线程池或进程池中安全地运行同步函数使其不会阻塞事件循环。这可以通过loop.run_in_executor(None, fetch_data)实现其中None参数使asyncio使用默认的线程池执行器。示例代码importasyncioimporttimedeffetch_data():# 模拟耗时操作time.sleep(1)returnData fetchedasyncdefmain():loopasyncio.get_running_loop()resultawaitloop.run_in_executor(None,fetch_data)print(result)asyncio.run(main())这样你就可以在异步程序中安全地调用同步函数而不会阻塞整个事件循环。