首页  >  文章  >  后端开发  >  使用 Asyncio 进行异步编程

使用 Asyncio 进行异步编程

PHPz
PHPz原创
2024-07-23 16:59:24677浏览

Asynchronous Programming with Asyncio

在编程世界中,“非阻塞”的概念无处不在。 JavaScript 开发人员经常使用术语“异步”,因为它是 JavaScript 的优势之一。然而,要真正理解异步编程,必须掌握并发和并行编程的概念。

并发编程

当多个独立实体同时工作时,编程是并发的。这并不一定意味着这些任务在完全相同的时间运行。相反,它意味着任务通过共享资源(例如 CPU 时间)随着时间的推移不断取得进展。并发编程的主要优点是它的鲁棒性:如果一个进程崩溃,程序的其余部分继续运行。

并行编程

如果一个算法可以将其工作分成几个部分,那么它就是并行的。拥有的处理器越多,您从并行性中受益就越多。高效的并行编程可优化现代机器的资源以获得更好的性能。

用 Cooking 说明并发与并行

并发示例:

想象一下您正在准备一顿饭,需要烤一些肉并制作酱汁。首先将肉放在烤架上。当肉烤的时候,你可以把西红柿和其他蔬菜切碎作为酱汁。然后,你开始煮酱汁,同时偶尔检查一下肉。在这里,两项任务(烤肉和制作酱汁)都在进行中,但您正在它们之间切换注意力。这代表并发。

并行示例:

现在,假设您有一位朋友可以帮助您。当您专注于烤肉时,您的朋友则负责制作酱汁。这两项任务同时完成,无需在它们之间切换注意力。这代表并行性。

什么是异步编程?

异步编程涉及处理程序外部发生的输入/输出 (I/O) 操作,例如用户输入、打印到终端、从套接字读取或写入磁盘。异步 I/O 的主要特征是:

  • 操作所花费的时间与CPU无关。相反,它取决于磁盘速度、网络延迟和其他外部条件等因素。

  • 程序无法预测操作何时结束。

对于具有大量 I/O 的服务(如 Web 服务器、数据库和部署脚本),优化这些操作可以极大地提高性能。

让我们看看阻塞代码和非阻塞代码的示例。

阻塞和非阻塞代码示例

考虑一个简单的程序:

import time

def task():
    time.sleep(2)
    print("Hello")

for _ in range(3):
    task()

在这个同步程序中,每个任务都会等待前一个任务完成,从而导致延迟。

现在,让我们看一下使用 asyncio 的异步版本:

import asyncio

async def task():
    await asyncio.sleep(2)
    print("Hello")

async def main():
    tasks = [task() for _ in range(3)]
    await asyncio.gather(*tasks)

asyncio.run(main())

在这个异步程序中,任务同时运行,减少了总执行时间。让我们探索异步编程的组件。

异步编程的组成部分

事件循环、协程和 future 是异步 Python 程序的基本元素。

  • 事件循环:管理任务切换和执行流程,跟踪要异步运行的任务。

  • 协程:可以暂停和恢复的特殊功能,允许在等待期间运行其他任务。协程指定任务切换事件应在函数中发生的位置,将控制权返回给事件循环。协程通常由事件循环创建并内部存储在任务队列中。

  • Futures: 协程结果的占位符,存储结果或异常。一旦事件循环启动一个协程,就会创建一个相应的 future 来存储协程的结果,如果在协程执行期间抛出异常,则会创建一个异常。

解释完 Python 异步编程的关键部分后,让我们编写一些代码。

编写异步代码

现在您已经了解了异步编程模式,让我们编写一个小脚本并分析执行情况。这是一个简单的异步脚本:

import asyncio

async def task():
    await asyncio.sleep(2)
    print("Hello")

async def main():
    tasks = [task() for _ in range(3)]
    await asyncio.gather(*tasks)

asyncio.run(main())

在上面的代码中,即使另一个正在执行的任务正在睡眠(阻塞),我们也尝试继续执行其他任务。注意任务和主函数前面的 async 关键字。

这些函数现在是协程

Coroutines functions in Python are preceded by the keyword async. The main() function here is the task coordinator or our single event loop, as it executes all tasks using the async.gather method. The asyncio.gather function runs awaitable objects concurrently.

Output:

Hello
Hello
Hello
Program executed in 2.01 seconds.

When each task reaches await asyncio.sleep(2), it simply goes to the next task and comes back when it's finished. It's like saying, "I am going to sleep for 2 seconds. Do something else."

Let's see the synchronous version for a quick comparison.

import time

def task():
    time.sleep(2)
    print("Hello")

for _ in range(3):
    task()

In the code above, we are going the traditional programming way in Python. You will notice that the execution of the process will take much more time.

Output:

Hello
Hello
Hello
Program executed in 6.01 seconds.

Now you can notice the execution time. Think of time.sleep() as a blocking task and asyncio.sleep() as a non-blocking or long task. In asynchronous programming, the benefit of awaiting something, like asyncio.sleep(), is that the surrounding function can temporarily cede control to another function that is ready to execute immediately.

With some basic examples of asynchronous programming in Python understood, let's explore the rules of asynchronous programming in Python.

Rules of Asyncio Programming

  1. Coroutines: Coroutines cannot be executed directly. If you try to run a coroutine function directly, it returns a coroutine object. Instead, use asyncio.run():

    import asyncio
    
    async def hello():
        await asyncio.sleep(1)
        print('Hello')
    
    asyncio.run(hello())
    
  2. Awaitable Objects: Coroutines, futures, and tasks are the main awaitable objects. Python coroutines are awaitables and can be awaited from other coroutines.

  3. Await Keyword:await can only be used within async functions.

    async def hello():
        await asyncio.sleep(1)
        print("Hello")
    
  4. Compatibility: Not all Python modules are compatible with asynchronous programming. For example, replacing await asyncio.sleep() with time.sleep() will cause an error. You can check the list of compatible and maintained modules here.

In the next section, we will explore a common use of asynchronous programming, HTTP requests.

Program Example: Asynchronous Requests

Let's take a look at the following piece of code:

import aiohttp
import asyncio

async def fetch(session, city):
    url = f"https://www.prevision-meteo.ch/services/json/{city}"
    async with session.get(url) as response:
        data = await response.json()
        print(f"Temperature at {city}: {data['current_condition']['tmp']} C")

async def main():
    async with aiohttp.ClientSession() as session:
        cities = ['paris', 'toulouse', 'marseille']
        tasks = [fetch(session, city) for city in cities]
        await asyncio.gather(*tasks)

asyncio.run(main())

In the code above, we create two asynchronous functions: one to fetch data from the prevision-meteo URL and a main function to execute the processes in the Python code. The goal is to send asynchronous HTTP GET requests to retrieve temperatures and print the responses.

In the main and fetch functions, we use async with. In the fetch function, async with ensures that the connection is closed properly. In the main function, it ensures that the ClientSession is closed after completing the requests. These practices are important in asynchronous coding in Python to manage resources efficiently and prevent leaks.

In the last line of the main function, we use await asyncio.gather(*tasks). In our case, it runs all tasks concurrently, allowing the program to send multiple HTTP requests simultaneously. Using await ensures that the program waits for all tasks to complete before proceeding.

Output:

Temperature at marseille: 25 C
Temperature at toulouse: 24 C
Temperature at paris: 18 C
Program executed in 5.86 seconds.

Synchronous Version for Comparison

Code:

import requests
import time

def fetch(city):
    url = f"https://www.prevision-meteo.ch/services/json/{city}"
    response = requests.get(url)
    data = response.json()
    print(f"Temperature at {city}: {data['current_condition']['tmp']} C")

def main():
    cities = ['paris', 'toulouse', 'marseille']
    for city in cities:
        fetch(city)

start_time = time.time()
main()
print(f"Program executed in {time.time() - start_time:.2f} seconds.")

Output:

Temperature at Paris: 18 C
Temperature at Toulouse: 24 C
Temperature at Marseille: 25 C
Program executed in 9.01 seconds.

When to Use Asynchronous Programming

The asynchronous model performs best when:

  • There are a large number of tasks, ensuring at least one task can always progress.

  • Tasks involve significant I/O, causing an asynchronous program to waste lots of time blocking when other tasks could be running.

  • Tasks are largely independent, minimizing inter-task communication (and thus for one task to wait upon another).

Conclusion

In this tutorial, we covered:

  • The concepts of asynchronous programming and related concepts.

  • Effective use of async/await.

  • Making asynchronous HTTP requests with aiohttp.

  • The benefits of asynchronous programming.

Thanks for reading. The second part will cover asynchronous programming with Django.

Resources

  • Python Documentation: Coroutines and Tasks

  • Python Documentation: asyncio - Asynchronous I/O

  • aiohttp Documentation

  • Aio Libraries

  • Concurrency vs Parallelism

以上是使用 Asyncio 进行异步编程的详细内容。更多信息请关注PHP中文网其他相关文章!

声明:
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn