Python进阶:Asyncio并发编程

一、Sync & Async

  • Sync(同步):是指操作一个接一个地执行,下一个操作必须等上一个操作完成后才能执行
  • Async(异步):是指不同操作间可以相互交替执行,如果其中的某个操作被block了,程序并不会等待,而是会找出可执行的操作继续执行

二、什么是Asyncio?

  • 和其它Python程序一样,是单线程的,只有一个主线程,但是可以进行多个不同的任务(task),这里的任务,就是特殊的future对象,这些不同的任务,被一个叫做event loop的对象所控制。可以把这里的任务,类比多线程版本里的多个线程

先来看一段简单的代码:

import time
def crawl_page(url):
	print('crawling {}'.format(url))
	sleep_time = int(url.split('_')[-1])
	time.sleep(sleep_time)
	print('OK {}'.format(url))

def main(urls):
	for url in urls:
		crawl_page(url)

if __name__ == '__main__':
    start_time = time.perf_counter()
    main(['url_1','url_2','url_3','url_4'])
    end_time = time.perf_counter()
    print('run {} seconds'.format(end_time - start_time))

上述代码是爬虫的简单示例,四个url一共用了10s的时间,如何用协程进行优化呢?

import time
import asyncio

async def crawl_page(url):
	print('crawling {}'.format(url))
	sleep_time = int(url.split('_')[-1])
	time.sleep(sleep_time)
	print('OK {}'.format(url))

async def main(urls):
	for url in urls:
		await crawl_page(url)

if __name__ == '__main__':
    start_time = time.perf_counter()
    asyncio.run(main(['url_1','url_2','url_3','url_4']))
    end_time = time.perf_counter()
    print('run {} seconds'.format(end_time - start_time))
  • async:声明异步函数,上面的crawl_page和main都成了异步函数
  • await:协程的关键字,后面接异步函数

1、当遇到 await 关键字时,协程会暂停自己的执行。
2、执行 await 后面的异步函数调用,将控制权交给异步函数。
3、异步函数开始执行异步操作,可能会暂时挂起自己的执行,等待异步操作完成。
4、在异步操作完成后,异步函数会恢复执行,并返回结果。
5、协程接收到异步函数的返回结果,恢复执行 await 语句后面的代码。
总结一下就是两个作用:
a、暂停当前协程的执行,将控制权交给事件循环(Event Loop),允许其他任务继续执行。在等待的过程中,协程不会阻塞整个程序或线程,而是允许其他协程或任务继续执行
b、等待异步操作的完成,并获取其返回的结果

  • asyncio.run:作为主程序的入口函数,在程序运行周期内,只调用一次asyncio.run

运行上述代码发现还需要10s,原因是await关键字是同步调用,crawl_page(url)在当前的调用结束之前,是不会触发下一次调用的,相当于用异步接口写了同步代码

import time
import asyncio

async def crawl_page(url):
    print('crawling {}'.format(url))
    sleep_time = int(url.split('_')[-1])
    await asyncio.sleep(sleep_time)
    print('OK {}'.format(url))

async def main(urls):
    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
    for task in tasks:
        await task

if __name__ == '__main__':
    start_time = time.perf_counter()
    asyncio.run(main(['url_1','url_2','url_3','url_4']))
    end_time = time.perf_counter()
    print('run {} seconds'.format(end_time - start_time))

上述代码只需要4s左右,运行总时长等于运行时间最长的爬虫

  • asyncio.create_task():返回一个Task对象,Task对象表示一个可调度的协程任务,可以被事件循环调度和执行,有了task对象之后,协程任务才算真正生效

对于task,还有另一种写法?

import time
import asyncio

async def crawl_page(url):
    print('crawling {}'.format(url))
    sleep_time = int(url.split('_')[-1])
    await asyncio.sleep(sleep_time)
    print('OK {}'.format(url))

async def main(urls):
    tasks = [asyncio.create_task(crawl_page(url)) for url in urls]
    await asyncio.gather(*tasks)

if __name__ == '__main__':
    start_time = time.perf_counter()
    asyncio.run(main(['url_1','url_2','url_3','url_4']))
    end_time = time.perf_counter()
    print('run {} seconds'.format(end_time - start_time))
  • asyncio.gather:并发执行多个协程任务的函数,接受一系列的协程对象,并返回一个协程对象,该协程对象表示所有任务的集合
  • *tasks:解包列表,将列表转换为函数的参数;与之对应的 * *dict可以将字典转化为函数的参数

其它用法:

import asyncio


async def worker_1():
        await asyncio.sleep(1)
        return 1

async def worker_2():
        await  asyncio.sleep(2)
        return 2 / 0

async def worker_3():
        await asyncio.sleep(3)
        return 3

async def main():
        task_1 = asyncio.create_task(worker_1())
        task_2 = asyncio.create_task(worker_2())
        task_3 = asyncio.create_task(worker_3())

        await asyncio.sleep(2)
        task_3.cancel()

        res = await asyncio.gather(task_1, task_2, tacansk_3, return_exceptions=True)
        print(res)

if __name__ == '__main__':
        asyncio.run(main())
  • cancal():取消方法,可取消任务
  • return_exceptions = True:如果不设置这个参数,错误就会完整地throw到执行层,从而需要try…catch来捕获,这也就意味着其它还没被执行的任务会被全部取消掉
  • res:上面说过await可以获取异步操作返回的结果

三、Asyncio工作原理

假设任务只有两个状态:一是预备状态;二是等待状态。所谓预备状态,是指任务目前空闲,但随时待命准备运行。而等待状态,是指任务已经运行,而正在等待外部的操作完成,比如I/O操作
在这种情况下,event loop会维护两个任务列表,分别对应这两种状态;并且选取预备状态的一个任务,使其运行,一直到把这个任务把控制权交还给event loop为止

当任务把控制权交还给event loop时,event loop会根据其是否完成,把任务放到预备或等待状态的列表,然后遍历等待状态列表的任务,查看它们是否完成,如果完成,则将其放到预备状态的列表,如果未完成,则继续放在等待状态的列表,如此周而复始,直到所有的任务都完成

四、Asyncio与多线程区别

  • ASyncio可以在不使用多线程的情况下实现并发编程,而且程序运行过程中不容易被打断,不容易出现race condition
  • 但是实际情况下,想用好Asyncio,很多情况下必须得有相应的Python库支持,Asyncio软件库的兼容性是个问题
  • 对于和多线程的区别,下面是个总结

如果是I/O heavy,并且I/O操作很慢,需要很多任务/线程协同运行,那么使用Asyncio更合适;
如果是I/O heavy,但是I/O操作很快,只需要有限数量的任务/线程,那么使用多线程就可以
如果是CPU heavy,则可以使用多进程来解决问题

  • 24
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python asyncio是一个用于编写并发代码的库。它提供了一种基于协程的并发编程模型,可以使编写异步代码更加简单和高效。通过使用async/await关键字,可以编写类似于同步代码的异步代码。 在使用asyncio并发编程时,可以使用事件循环(Event Loop)来调度协程任务的执行。事件循环是一个无限循环,不断地从任务队列中取出任务并执行。每当遇到一个需要等待的操作时,比如IO操作或者等待其他任务完成,事件循环会将当前任务挂起,并切换到下一个可执行的任务。一旦等待的操作完成,事件循环会恢复之前挂起的任务的执行。 下面是一个简单的示例代码,展示了如何使用asyncio并发编程: ```python import asyncio async def factorial(n): if n == 0: return 1 else: return n * await factorial(n - 1) async def main(): tasks = [factorial(i) for i in range(5)] results = await asyncio.gather(*tasks) print(results) if __name__ == '__main__': asyncio.run(main()) ``` 在上面的代码中,我们定义了一个factorial函数,使用递归计算乘。然后,我们定义了一个main函数,其中创建了一组协程任务,并使用asyncio.gather函数来等待所有任务完成并获取结果。最后,我们使用asyncio.run函数来运行主函数。 这只是一个简单的示例,实际应用中可能会涉及更复杂的并发场景。但是,通过使用asyncio,我们可以更方便地编写高效的并发代码,充分利用计算机的多核性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱夜来香A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值