赞
踩
并发编程是一种处理多个任务同时执行的编程方式,在Python中,asyncio
是一种用于实现异步编程的强大工具。asyncio
基于协程(coroutine)的概念,能够高效地处理I/O密集型任务。本文将介绍asyncio
的基本原理和使用方法。
asyncio
我们知道,在处理 I/O 操作时,使用多线程与普通的单线程相比,效率得到了极大的提高。既然这样,为什么还需要 Asyncio?
多线程有诸多优点且应用广泛,但也存在一定的局限性:
正是为了解决这些问题,Asyncio 应运而生。
我们首先来区分一下 Sync(同步)和 Async(异步)的概念。
Asyncio
使用协程来实现异步操作。协程是一种特殊的函数,使用 async
关键字定义。在协程中,可以使用 await
关键字暂停当前协程的执行,等待一个异步操作完成。Asyncio
的核心机制之一。它负责调度和执行协程,并处理协程之间的切换。事件循环会不断地轮询可执行的任务,一旦某个任务就绪(如 IO 完成或定时器到期),事件循环会将其放入执行队列并继续执行下一个任务。Asyncio
中,我们通过创建异步任务来执行协程。异步任务由 asyncio.create_task()
函数创建,它将协程封装成一个可等待对象,并提交给事件循环进行处理。Asyncio
提供了一组异步的 IO 操作(如网络请求、文件读写等),这些操作可以通过协程和事件循环无缝地进行集成。通过使用异步 IO 操作,可以避免在等待 IO 完成时发生阻塞,提高程序的性能和并发性。Asyncio
也支持使用回调函数处理异步操作的结果。可以通过使用 asyncio.ensure_future()
函数将回调函数封装为一个可等待对象,并提交给事件循环进行处理。Asyncio
可以并发执行多个协程任务。事件循环会根据任务的就绪情况自动调度协程的执行,从而实现高效的并发编程。总结起来,Asyncio
的工作原理是基于协程和事件循环的机制。通过使用协程进行异步操作,并由事件循环负责协程的调度和执行,Asyncio
实现了高效的异步编程模型。
协程是asyncio
中的重要概念,它是一种轻量级的执行单位,可以在任务之间进行快速切换而无需线程切换的开销。协程可以通过async
关键字定义,而await
关键字用于暂停协程的执行,等待某个操作完成后再继续执行。
以下是一个简单的示例代码,演示了如何使用协程进行异步编程:
- import asyncio
-
- async def hello():
- print("Hello")
- await asyncio.sleep(1) # 模拟耗时操作
- print("World")
-
- # 创建一个事件循环
- loop = asyncio.get_event_loop()
-
- # 将协程加入事件循环并执行
- loop.run_until_complete(hello())
在这个示例中,函数hello()
是一个协程,通过async
关键字进行定义。在协程内部,我们可以使用await
来暂停协程的执行,这里使用asyncio.sleep(1)
模拟一个耗时操作。通过run_until_complete()
方法,将协程加入事件循环并运行。
asyncio
主要用于处理I/O密集型任务,如网络请求、文件读写等操作。它提供了一系列的异步I/O操作API,可与await
关键字配合使用,轻松实现异步编程。
以下是一个简单的示例代码,展示了如何使用asyncio
进行异步网络请求:
- import asyncio
- import aiohttp
-
- async def fetch(session, url):
- async with session.get(url) as response:
- return await response.text()
-
- async def main():
- async with aiohttp.ClientSession() as session:
- html = await fetch(session, 'https://www.example.com')
- print(html)
-
- # 创建一个事件循环
- loop = asyncio.get_event_loop()
-
- # 将协程加入事件循环并执行
- loop.run_until_complete(main())
在这个示例中,我们使用了aiohttp
库进行网络请求。函数fetch()
是一个协程,通过session.get()
方法发起异步GET请求,并通过await
关键字等待响应返回。函数main()
是另一个协程,内部创建了一个ClientSession
对象来重复使用,然后调用fetch()
方法获取网页内容并打印。
注意:
看到这里我们使用了aiohttp
而没有使用requests
库,是因为requests 库并不兼容 Asyncio,但是 aiohttp 库兼容。
想用好 Asyncio,特别是发挥其强大的功能,很多情况下必须得有相应的 Python 库支持
asyncio
还提供了一些用于并发执行多个任务的机制,如asyncio.gather()
和asyncio.wait()
等。下面是一个示例代码,展示了如何使用这些机制并发执行多个协程任务:
- import asyncio
-
- async def task1():
- print("Task 1 started")
- await asyncio.sleep(1)
- print("Task 1 finished")
-
- async def task2():
- print("Task 2 started")
- await asyncio.sleep(2)
- print("Task 2 finished")
-
- async def main():
- await asyncio.gather(task1(), task2())
-
- # 创建一个事件循环
- loop = asyncio.get_event_loop()
-
- # 将协程加入事件循环并执行
- loop.run_until_complete(main())
在这个示例中,我们定义了两个协程任务task1()
和task2()
,它们都进行了一些耗时操作。协程main()
通过asyncio.gather()
同时启动这两个任务,并等待它们完成。通过并发执行,可以提高程序的执行效率。
实际项目中到底选择多线程还是asyncio
呢?有位大佬这样总结的,很形象
- if io_bound:
- if io_slow:
- print('Use Asyncio')
- else:
- print('Use multi-threading')
- else if cpu_bound:
- print('Use multi-processing')
输入一个列表,对于列表中的每个元素,我想计算 0 到这个元素的所有整数的平方和。
同步实现
- import time
-
- def cpu_bound(number):
- return sum(i * i for i in range(number))
-
-
- def calculate_sums(numbers):
- for number in numbers:
- cpu_bound(number)
-
-
- def main():
- start_time = time.perf_counter()
- numbers = [10000000 + x for x in range(20)]
- calculate_sums(numbers)
- end_time = time.perf_counter()
- print('Calculation takes {} seconds'.format(end_time - start_time))
-
-
- if __name__ == '__main__':
- main()
执行时间需要Calculation takes 17.976343413000002 seconds
异步实现
concurrent.futures
实现
- import time
- from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor, as_completed
-
- def cpu_bound(number):
- return sum(i * i for i in range(number))
-
-
- def calculate_sums(numbers):
- with ProcessPoolExecutor() as executor:
- results = executor.map(cpu_bound, numbers)
- results = [result for result in results]
- print(results)
-
- def main():
- start_time = time.perf_counter()
- numbers = [10000000 + x for x in range(20)]
- calculate_sums(numbers)
- end_time = time.perf_counter()
- print('Calculation takes {} seconds'.format(end_time - start_time))
-
-
- if __name__ == '__main__':
- main()
执行时间需要Calculation takes 7.314132894999999 seconds
.
在这个改进的代码中,我们使用 concurrent.futures.ProcessPoolExecutor
来创建一个进程池,然后通过 executor.map()
方法来提交任务和获取结果。
请注意,在使用 executor.map()
后,如果需要获取结果,可以将结果迭代为一个列表,或者使用其他方法对结果进行处理。
multiprocessing
实现
- import time
- import multiprocessing
-
-
- def cpu_bound(number):
- return sum(i * i for i in range(number))
-
-
- def calculate_sums(numbers):
- with multiprocessing.Pool() as pool:
- pool.map(cpu_bound, numbers)
-
-
- def main():
- start_time = time.perf_counter()
- numbers = [10000000 + x for x in range(20)]
- calculate_sums(numbers)
- end_time = time.perf_counter()
- print('Calculation takes {} seconds'.format(end_time - start_time))
-
-
- if __name__ == '__main__':
- main()
执行用时Calculation takes 6.051121667 seconds
concurrent.futures.ProcessPoolExecutor
和 multiprocessing
都是 Python 中用于实现多进程并发的库,它们有一些区别。
concurrent.futures.ProcessPoolExecutor
是 concurrent.futures
模块提供的一个高级接口,它对底层的多进程功能进行了封装,使得编写多进程代码更加简单。而 multiprocessing
则是 Python 的标准库之一,提供了完整的多进程支持,并允许直接操作进程。concurrent.futures.ProcessPoolExecutor
的使用方式类似于线程池,它通过提交可调用对象(如函数)到进程池中执行,并返回一个 Future
对象,可以用来获取执行结果。而 multiprocessing
提供了更底层的进程管理和通信接口,可以显式地创建、启动和控制进程,并使用多个进程之间的队列或管道进行通信。multiprocessing
提供了更底层的接口,因此它相对于 concurrent.futures.ProcessPoolExecutor
来说更加灵活。通过直接操作进程,可以对每个进程进行更细粒度的控制,如设置进程优先级、进程间共享数据等。而 concurrent.futures.ProcessPoolExecutor
更适合于简单的任务并行化,它隐藏了许多底层的细节,使得编写多进程代码更加简单和易用。concurrent.futures.ProcessPoolExecutor
和 multiprocessing
都提供了跨平台的多进程支持,可以在各种操作系统上使用。综上所述,concurrent.futures.ProcessPoolExecutor
是一个高级接口,封装了底层的多进程功能,适用于简单的多进程任务并行化。而 multiprocessing
是一个更底层的库,提供了更多的控制和灵活性,适用于需要精细控制进程的场景。
需要根据具体需求选择合适的库,如果只是简单的任务并行化,可以使用 concurrent.futures.ProcessPoolExecutor
来简化代码;如果需要更底层的控制和通信,可以使用 multiprocessing
库。
不同于多线程,Asyncio 是单线程的,但其内部 event loop 的机制,可以让它并发地运行多个不同的任务,并且比多线程享有更大的自主控制权。
Asyncio 中的任务,在运行过程中不会被打断,因此不会出现 race condition 的情况。
尤其是在 I/O 操作 heavy 的场景下,Asyncio 比多线程的运行效率更高。因为 Asyncio 内部任务切换的损耗,远比线程切换的损耗要小;并且 Asyncio 可以开启的任务数量,也比多线程中的线程数量多得多。
但需要注意的是,很多情况下,使用 Asyncio 需要特定第三方库的支持,比如前面示例中的 aiohttp。而如果 I/O 操作很快,并不 heavy,那么运用多线程,也能很有效地解决问题。
asyncio
是用于实现异步编程的Python库。asyncio
的核心概念,通过async
和await
关键字实现异步操作。asyncio
提供了强大的异步I/O操作API,可轻松处理I/O密集型任务。asyncio.gather()
等机制,可以并发执行多个协程任务。最后:下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保100%免费】
我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。