irpas技术客

〖Python语法进阶篇⑧〗- 异步关键字与gevent包_全栈哈士奇_python 异步包

大大的周 8424

万叶集🎉 隐约雷鸣,阴霾天空。 🎉🎉 但盼风雨来,能留你在此。 🎉

前言: ? 作者简介:渴望力量的哈士奇 ?,大家可以叫我 🐶哈士奇🐶 ,一位致力于 TFS 赋能的博主 ? 🏆 CSDN博客专家认证、新星计划第三季全栈赛道 top_1 、华为云享专家、阿里云专家博主 🏆 📫 如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀 💬 人生格言:优于别人,并不高贵,真正的高贵应该是优于过去的自己。💬 🔥 如果感觉博主的文章还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主哦


专栏系列(点击解锁)学习路线指引知识定位 🔥Python全栈白皮书🔥 零基础入门篇 以浅显易懂的方式轻松入门,让你彻底爱上Python的魅力。 语法进阶篇 主要围绕多线程编程、正则表达式学习、含贴近实战的项目练习 。 自动化办公篇 实现日常办公软件的自动化操作,节省时间、提高办公效率。 自动化测试实战篇 从实战的角度出发,先人一步,快速转型测试开发工程师。 数据库开发实战篇 更新中 爬虫入门与实战 更新中 数据分析篇 更新中 前端入门+flask 全栈篇 更新中 django+vue全栈篇 更新中 拓展-人工智能入门 更新中 网络安全之路 踩坑篇 记录学习及演练过程中遇到的坑,便于后来居上者 网安知识扫盲篇 三天打鱼,不深入了解原理,只会让你成为脚本小子。 vulhub靶场漏洞复现 让漏洞复现变得简单,让安全研究者更加专注于漏洞原理本身。 shell编程篇 不涉及linux基础,最终案例会偏向于安全加固方向。 [待完结] WEB漏洞攻防篇 2021年9月3日停止更新,转战先知社区等安全社区及小密圈 渗透工具使用集锦 2021年9月3日停止更新,转战先知社区等安全社区及小密圈 点点点工程师 测试神器 - Charles 软件测试数据包抓包分析神器 测试神器 - Fiddler 一文学会 fiddle ,学不会倒立吃翔,稀得! 测试神器 - Jmeter 不仅是性能测试神器,更可用于搭建轻量级接口自动化测试框架。 RobotFrameWork Python实现的自动化测试利器,该篇章仅介绍UI自动化部分。 Java实现UI自动化 文档写于2016年,Java实现的UI自动化,仍有借鉴意义。 MonkeyRunner 该工具目前的应用场景已不多,文档已删,为了排版好看才留着。


文章目录 🐳 初探异步🐬 什么是异步与异步的好处🐬 异步与多线程、多进程的对比 🐳 异步的关键字与模块🐬 异步关键字 - async 与 await🐬 asyncio模块 - gather 与 run 函数的使用 🐳 第三方异步包 - gevent🐬 gevent 模块的常用方法 🐳 异步案例 - 捡豆子🐬 async 关键字实现 "捡豆子"🐬 gevent 包实现 "捡豆子" 🐳 总结


在之前的章节中,我们学习了多线程与多线程的使用方法。它们有一个共同的特点,就是在主进程或者说主线程中创建多个子进程或子线程,子进程与子线程的运行不会影响主进程或主线程的代码执行。从而使得代码在可以在多个进程或多个线程的工作下提高工作效率。今天我们就来学习多进程与多线程的另一个知识点 —> 异步 。

异步 其实在我们前面章节的进程池与线程池的时候已经间接的接触过了,今天就好好的了解一下,看看 异步 在平时的工作中都有哪些优势和使用场景。

🐳 初探异步 🐬 什么是异步与异步的好处

我们曾经在 Python 的脚本结构介绍过,python脚本的执行顺序是自上而下逐行执行的(处于下方的代码会等待上方的代码执行完之后才会执行)。

不过 异步 则有些不同,举个例子。比如我们当前的脚本中有5个任务,其中第二个任务需要执行比较长的时间才会结束,当第二个任务执行完毕之后才会去执行后面的任务,这样必然会消耗比较长的时间。

当我们希望任务二虽然需要很长的一段运行时间却又不影响后续的任务执行的时候,就需要将任务二从同步的状态变为 异步 执行的状态。可以认为 异步的执行 不会影响或者说不会阻塞主程序的执行。

正常状态下,任务二的执行需要任务一完成之后才会开始。相对于任务二来说,任务一等于阻塞了任务二的执行。而如果任务二是异步的,当任务二执行完毕之后,任务四不会受到任务三的影响。也就是说任务三不会阻塞任务四 ,可以继续的正常执行。

这就是 异步 ,听起来好像与 多进程、多线程 类似,的确如此。多进程、多线程与异步之间的关系有点类似于兄弟之间的关系似的。接下来让我们看一下 异步与多进程、多线程之间有何相同又有何不同。

🐬 异步与多线程、多进程的对比

首先要确定一下身份,异步 也属于一种线程,只不过它属于一种轻量级的线程,我们也把它叫做 协程 。通过前面章节的学习我们知道线程是进程下的一部分,同样的 "协程" 也是进程下的一部分。 而与多进程、多线程不同的是,多进程与多线程无法获取函数的返回值,但是 “异步” 是可以获取到函数的返回值的(之前章节的进程池与线程池的演示效果,其实就是 "异步" 的效果,所以它们可以获取返回值。)。

听起来似乎 “异步” 更适合平时的使用,其实不然,它们都有各自擅长使用的场景和条件。

比如 “多进程与多线程” 可以随时创建使用,虽然可能 多进程 的每次创建都会消耗一定的资源。相比于 “多进程与多线程” 来说, “异步” 的执行就有一些苛刻了,他们必须保证主进程是在异步的情况下,才可以使用。比如后续的 WEB 开发阶段,如果启动WEB服务是一个异步的服务,我们才可以比较轻松的使用异步。

另外,异步的要求是执行的过程中所有的程序都需要是异步才可以使用。(这对于初学者来说不太好理解,没关系。现阶段只需要对此有一个了解即可,等到 WEB开发阶段的时候可能就会豁然开朗…[不开朗就重新学一遍!])

可能大家会有个疑问,我们目前还没有接触 WEB 开发,是不是就没有办法使用异步了呢?其实不是,今天的章节也会涉及到如何在脚本中使用异步的案例。因为 异步可以获取返回值 ,所以 异步在平时的工作中更适合于文件读写相关需要获取返回值的场景 ;而多进程与多线程则更适合业务方向的处理,不需要返回值的相关工作。

🐳 异步的关键字与模块

现在我们对 异步的相关知识有一个大体上的了解,接下来就学习一下 Python 中的关键字 async 与 await,它们是在 Python 3.4之后才引进的新的功能。

🐬 异步关键字 - async 与 await

async 的功能介绍:代表定义异步;无论是多线程、多进程还是异步,都是对函数进行操作。所以如果想要函数编程异步,就需要在函数的前面加上 async 关键字。 示例如下:

async def test(): return 'test'

await 的 功能介绍:当定义了异步函数之后,需要使用 await 关键字来执行异步函数。示例如下:

async def deal (): result = await test() # 在 test() 函数之前添加关键字 await

这里需要注意的是:如果 test() 没有使用 async 关键字进行异步声明,则无法通过使用 await 进行异步的调用。

这个时候大家可能又会有个疑问了,main 主函数作为程序的入口它无法定义 async,这个时候又怎么能执行异步函数呢?如果是在异步的 WEB服务中自然不需要有这些顾虑,但现阶段我们是在脚本中运行就需要一个帮手的帮助了,就是 Python 中的内置模块 — async 模块。通过 async 模块 来调用异步函数的执行,async 模块有很多异步的功能,这里仅介绍两个功能,来帮助我们认识异步与其使用方法。

🐬 asyncio模块 - gather 与 run 函数的使用 函数名介绍参数返回值gather将异步函数批量执行asyncfunc…List - 函数的返回结果run执行主异步函数[task]执行函数的返回结果
gather 函数:可以将异步函数一起执行,不论数量是多少,并且可以按照顺序获取这些异步函数的返回值:它的返回值是一个列表;它的参数可以传入多个声明了 async 关键字的函数。run 函数:可以执行一个单独的声明了 async 关键字的函数,并且获取这个异步函数的返回值

PS:需要注意的是如果想要使用异步那么我们的整个环境都需要是异步才行。

接下来看一个小案例:

# coding:utf-8 import time import random import asyncio async def test_a(): # 定义一个测试函数 test_a for i in range(5): _start_time = time.time() time.sleep(random.random() * 2) _end_time = time.time() print('这是 \'test_a()\' 函数的第 {} 循环,随机休眠时间为:{}'.format(i, _end_time - _start_time)) async def test_b(): # 定义一个测试函数 test_a for i in range(5): _start_time = time.time() time.sleep(random.random() * 2) _end_time = time.time() print('这是 \'test_b()\' 函数的第 {} 循环,随机休眠时间为:{}'.format(i, _end_time - _start_time)) async def main(): # 利用 async 关键字 异步声明一个 main 函数 result = await asyncio.gather( # 调用 asyncio模块的gather函数 并传入 test_a 与 test_b 函数,并获取返回值。 test_a(), test_b() ) print(result) if __name__ == '__main__': # 在脚本主函数入口,调用 asyncio模块的run函数 启动上面 异步声明的main函数 asyncio.run(main())

该脚本等于说模拟 main 函数是一个主进程,并且该主进程已经通过 async 关键字声明了异步;接下来使用 asyncio模块的run函数来调用,从而达到进程下都是一个异步环境的效果。

运行效果如下:


这里发现一个问题,脚本的运行结果依然是同步运行的,并没有达到我们想要的异步效果。这是为什么呢?其实问题就出在了 time.sleep() 上面,因为 time.sleep() 实际上是 CPU级别的阻塞 ,如果CPU阻塞了也是无法实现异步效果的。 所以这里就需要改为 asyncio.sleep(random.random() ,但由于 asyncio.sleep() 也是异步的,所以还需要加上 await 才可以。


修改后的脚本如下:

# coding:utf-8 import time import random import asyncio async def test_a(): # 定义一个测试函数 test_a for i in range(5): _start_time = time.time() await asyncio.sleep(random.random() * 2) # time.sleep() 是CPU级别的阻塞所以需要修改为 asyncio.sleep() 实现异步 # 但由于 asyncio.sleep() 也是异步的,所以还需要加上 await 才可以 _end_time = time.time() print('这是 \'test_a()\' 函数的第 {} 次循环,随机休眠时间为:{} 秒'.format(i+1, _end_time - _start_time)) return '\'test_a\' 函数运行结束' async def test_b(): # 定义一个测试函数 test_a for i in range(5): _start_time = time.time() await asyncio.sleep(random.random() * 2) _end_time = time.time() print('这是 \'test_b()\' 函数的第 {} 次循环,随机休眠时间为:{} 秒'.format(i+1, _end_time - _start_time)) return '\'test_b\' 函数运行结束' async def main(): # 利用 async 关键字 异步声明一个 main 函数 result = await asyncio.gather( # 调用 asyncio模块的gather函数 并传入 test_a 与 test_b 函数,并获取返回值。 test_a(), test_b() ) print(result) if __name__ == '__main__': # 在脚本主函数入口,调用 asyncio模块的run函数 启动上面 异步声明的main函数 start_time = time.time() asyncio.run(main()) end_time = time.time() print('共计耗时为:%s' % (end_time - start_time))

运行效果如下:

这次的运行效果我们可以看到已经实现了异步的效果,而且耗时上也比上次的执行减少到了 5秒钟。

这里大家可以将异步的进程号和主函数的进程号也打印出来,届时会发现异步的进程号与主函数的进程号是一样的。也就证明了异步实际上一种类似于线程的存在,就像文章开头说的,它是一个轻量级的微小线程(我们也把异步叫做协程),其实协程还有更多的执行的原理,我们会在未来学习了更高级的知识之后再去理解。


🐳 第三方异步包 - gevent

接下里让我们继续看一下今天的第二个异步包 ---> gevent 。 gevent 为 Python 提供异步支持的时间要早于 async ,早在 Python 2.x 的时代它就已经存在了。

首先,来看一下如何使用 gevent。

通过 pip install gevent 来安装 gevent 包。

如果是使用 Windows 系统的话,还需要安装 Microsoft Visual C++ 的依赖

有些 Linux 环境也会需要依赖 wheel 的包,pip install wheel 。

🐬 gevent 模块的常用方法

来看一下 gevent 常用的两个函数

函数名介绍参数返回值spawn创建协程对象Func, args协程对象joinall批量处理协程对象[spawnpbj][spawnpbj]


接下来我们根据上文的异步脚本修改为通过 gevent 实现的方式:

# coding:utf-8 import os import time import random import asyncio import gevent def gevent_test_a(): # 定义一个测试函数 test_a for i in range(5): _start_time = time.time() gevent.sleep(random.random() * 2) # time.sleep() 是CPU级别的阻塞所以需要修改为 gevent.sleep() 实现异步 _end_time = time.time() print('这是 \'gevent_test_a\' 函数的第 {} 次循环, 随机休眠时间为:{} 秒, 进程号为:{} 。'.format(i+1, _end_time - _start_time, os.getpid())) return '\'gevent_test_a\' 函数运行结束' def gevent_test_b(): # 定义一个测试函数 test_a for i in range(5): _start_time = time.time() gevent.sleep(random.random() * 2) _end_time = time.time() print('这是 \'gevent_test_b\' 函数的第 {} 次循环, 随机休眠时间为:{} 秒, 进程号为:{} 。'.format(i+1, _end_time - _start_time, os.getpid())) return '\'gevent_test_b\' 函数运行结束' if __name__ == '__main__': # 在脚本主函数入口,调用 asyncio模块的run函数 启动上面 异步声明的main函数 start_time = time.time() gevent_test_a = gevent.spawn(gevent_test_a) gevent_test_b = gevent.spawn(gevent_test_b) gevent_lists = [gevent_test_a, gevent_test_b] result = gevent.joinall(gevent_lists) print(result) end_time = time.time() print('共计耗时为:%s' % (end_time - start_time), '主函数的进程号为:%s' % os.getpid())

运行结果如下:



🐳 异步案例 - 捡豆子 🐬 async 关键字实现 “捡豆子”

代码示例如下:

# coding:utf-8 import random import asyncio """ async 定义异步函数 await 执行异步 gather 将异步函数批量执行 run 执行主异步函数 """ beans = list(range(1, 51)) # 豆子总数 async def child_a(): child_a_beans = [] while 1: date = random.choice(beans) child_a_beans.append(date) beans.remove(date) await asyncio.sleep(random.random()) if not beans: break # global child_a_beans return 'child_a捡到的豆子有: {} ,共计: {} 个'.format(child_a_beans, len(child_a_beans)), len(child_a_beans) async def child_b(): child_b_beans = [] while 1: date = random.choice(beans) child_b_beans.append(date) beans.remove(date) await asyncio.sleep(random.random()) if not beans: break # global child_b_beans return 'child_a捡到的豆子有: {} ,共计: {} 个'.format(child_b_beans, len(child_b_beans)), len(child_b_beans) async def main(): result = await asyncio.gather( child_a(), child_b()) return result if __name__ == '__main__': result = asyncio.run(main()) # print(type(result[0][-1])) # print(result[1][-1]) print("a_beans:", result[0]) print("b_beans:", result[1]) if result[0][1] > result[1][1]: print('child_a赢了') elif result[0][1] < result[1][1]: print('child_b赢了') else: print('child_a 与 child_b 平局')

执行结果如下:



🐬 gevent 包实现 “捡豆子”

代码示例如下:

# coding:utf-8 import random import gevent """ spawn 创建协程对象 joinall 批量处理协程对象 """ # 豆子总数 beans = list(range(1, 51)) def child_a(): a_beans = [] while 1: date = random.choice(beans ) # 随机获取豆子 a_beans.append(date) # 把随机获取到的豆子添加到a_beans列表中 beans.remove(date) # 并删除获取到的豆子 gevent.sleep(random.random()) # 执行随机阻塞时间 注意:随机时间越长获取到的豆子就越多 #gevent.sleep(0.1) 阻塞时间相等获取到的豆子数量也相等 if not beans: break return 'child_a捡到的豆子有%s,总共%s个' % (a_beans, len(a_beans )) def child_b(): b_beans = [] while 1: date = random.choice(beans) b_beans.append(date) beans.remove(date) gevent.sleep(random.random()) # gevent.sleep(0.1) 阻塞时间相等获取到的豆子数量也相等 if not beans: break return 'child_b捡到的豆子有%s,总共%s个' % (b_beans, len(b_beans)) if __name__ == '__main__': g_a = gevent.spawn(child_a) g_b = gevent.spawn(child_b) result = gevent.joinall([g_a, g_b]) print("a_beans", result[0].value) print("b_beans", result[1].value)

执行结果如下:



🐳 总结

通过 gevent 的这种方式实现的异步,几乎与 async 的效果完全是一致的(它们都是将协程放在一起去批量的执行)。

今天作为异步的启蒙,我们也仅仅是针对异步做了初步的了解,以及常用模块的基本使用方法。知识的积累也是伴随着经验和成长的积累,很多只是和经验都是在适当的时间获取才会更加合适,今天关于异步的内容就讲到这里了。

(累了...)


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Python #异步包 #gt #异步