22. 协程与Python中的多任务异步协程

2023-11-10

目录

前言

协程概念

示例代码

Python编写协程程序

要用到的库函数

尝试编写异步

尝试改进函数

尝试优化代码

在爬虫领域的应用 

总结


前言

本节我们介绍一个新概念:协程。协程顾名思义,是协助执行程序的过程。我们将介绍协程的概念和其在Python中的应用。


协程概念

百度百科(协程):

协程不是进程或线程,其执行过程更类似于子例程,或者说不带返回值的函数调用

一个程序可以包含多个协程,可以对比与一个进程包含多个线程,因而下面我们来比较协程和线程。我们知道多个线程相对独立,有自己的上下文,切换受系统控制;而协程也相对独立,有自己的上下文,但是其切换由自己控制,由当前协程切换到其他协程由当前协程来控制。

注意,协程是应用程序内部切换上下文,不会进入内核进行线程上下文切换。

协程主要用于某一进程CPU调用睡眠时或者进行I/O操作时,选择性地切换到其他任务,从而提高效率。在微观上是一个任务一个任务的进行切换,切换条件一般就是IO操作。在宏观上,我们能看到的其实是多个任务一起在执行(多任务异步操作)


示例代码

import time


def func():
    print("我爱黎明")
    time.sleep(3)  # 让当前的线程处于阻塞状态. CPU是不为我工作的
    print("我真的爱黎明")


if __name__ == '__main__':
    func()

"""
# input() 程序也是处于阻塞状态
# requests.get(bilibili) 在网络请求返回数据之前, 程序也是处于阻塞状态的
# 一般情况下, 当程序处于 IO操作的时候. 线程都会处于阻塞状态

# 协程: 当程序遇见了IO操作的时候. 可以选择性的切换到其他任务上.
# 在微观上是一个任务一个任务的进行切换. 切换条件一般就是IO操作
# 在宏观上,我们能看到的其实是多个任务一起在执行
# 多任务异步操作

# 上方所讲的一切. 都是在单线程的条件下
"""

Python编写协程程序

要用到的库函数

# python编写协程的程序
import asyncio


async def func():
    print("你好啊, 我叫赛利亚")


if __name__ == '__main__':
    g = func()  # 此时的函数是异步协程函数. 此时函数执行得到的是一个协程对象
    # print(g)
    asyncio.run(g)  # 协程程序运行需要asyncio模块的支持

要编写异步程序,要用到asyncio这个库,它是Python自带的,直接导入就可以。我们写一个异步函数,它不能在主函数中直接运行,会抛出Error,因为它直接运行返回的是一个协程对象。我们必须用asyncio的run函数才能运行。

尝试编写异步

import asyncio
import time

async def func1():
    print("你好啊, 我叫李诞")
    time.sleep(3)  # 当程序出现了同步操作的时候. 异步就中断了
    print("你好啊, 我叫李诞")


async def func2():
    print("你好啊, 我叫王建国")
    time.sleep(2)
    print("你好啊, 我叫王建国")


async def func3():
    print("你好啊, 我叫李雪琴")
    time.sleep(4)
    print("你好啊, 我叫李雪琴")


if __name__ == '__main__':
    f1 = func1()
    f2 = func2()
    f3 = func3()
    tasks = [
        f1, f2, f3
    ]
    t1 = time.time()
    # 一次性启动多个任务(协程)
    asyncio.run(asyncio.wait(tasks))    # 固定搭配
    t2 = time.time()
    print(t2 - t1)

我们想一次性执行多个异步任务时,需要把它们放在列表中,并且用asyncio的run函数中嵌套wait函数才能实现,它是固定搭配,可以套公式。

打印程序执行时间,发现此时执行时间和串行执行的速度差不多——也是9秒多

(打印数据时间+3+2+4)秒

问题出在time.sleep()。当异步函数中出现同步操作时,异步就中断了,所以还是在等待睡眠时间中CPU什么都没有执行。

尝试改进函数

import asyncio
import time

async def func1():
    print("你好啊, 我叫李诞")
    # time.sleep(3)  # 当程序出现了同步操作的时候. 异步就中断了
    await asyncio.sleep(3)  # 异步操作的代码
    print("你好啊, 我叫李诞")


async def func2():
    print("你好啊, 我叫王建国")
    # time.sleep(2)
    await asyncio.sleep(2)
    print("你好啊, 我叫王建国")


async def func3():
    print("你好啊, 我叫李雪琴")
    # time.sleep(4)
    await asyncio.sleep(4)
    print("你好啊, 我叫李雪琴")


if __name__ == '__main__':
    f1 = func1()
    f2 = func2()
    f3 = func3()
    tasks = [
        f1, f2, f3
    ]
    t1 = time.time()
    # 一次性启动多个任务(协程)
    asyncio.run(asyncio.wait(tasks))
    t2 = time.time()
    print(t2 - t1)

我们将睡眠操作改为异步,尝试执行,打印执行时间为4秒多(最长的睡眠时间+调度时间)

但我们这样并不是最理想化的代码,我们将其进行优化:

尝试优化代码

import time
import asyncio

async def func1():
    print("你好啊, 我叫李诞")
    await asyncio.sleep(3)
    print("你好啊, 我叫李诞")


async def func2():
    print("你好啊, 我叫王建国")
    await asyncio.sleep(2)
    print("你好啊, 我叫王建国")


async def func3():
    print("你好啊, 我叫李雪琴")
    await asyncio.sleep(4)
    print("你好啊, 我叫李雪琴")


async def main():
    # 第一种写法
    # f1 = func1()
    # await f1  # 一般await挂起操作放在协程对象前面
    # 第二种写法(推荐)
    tasks = [
        asyncio.create_task(func1()),  # py3.8以后加上asyncio.create_task()
        asyncio.create_task(func2()),
        asyncio.create_task(func3())
    ]
    await asyncio.wait(tasks)


if __name__ == '__main__':
    t1 = time.time()
    # 一次性启动多个任务(协程)
    asyncio.run(main())
    t2 = time.time()
    print(t2 - t1)

这里还是推荐把函数放在tasks列表中,然后在main异步函数中异步执行异步函数列表,然后在主程序中调用main异步函数。我们依旧输出运行时间,查看是否成功异步运行:

可以看到是没问题的。


在爬虫领域的应用 

import asyncio

# 在爬虫领域的应用
async def download(url):
    print("准备开始下载")
    await asyncio.sleep(2)  # 网络请求  requests.get()
    print("下载完成")


async def main():
    urls = [
        "http://www.baidu.com",
        "http://www.bilibili.com",
        "http://www.163.com"
    ]

    # 准备异步协程对象列表
    tasks = []
    for url in urls:
        d = asyncio.create_task(download(url))
        tasks.append(d)

    # tasks = [asyncio.create_task(download(url)) for url in urls]  # 这么干也行哦~

    # 一次性把所有任务都执行
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

这里用睡眠代替了网络请求操作,相当于一个模板,以后要批量请求网页的时候可以套用。

运行结果:


完整代码

调试请自行修改注释部分

# import time
#
#
# def func():
#     print("我爱黎明")
#     time.sleep(3)  # 让当前的线程处于阻塞状态. CPU是不为我工作的
#     print("我真的爱黎明")
#
#
# if __name__ == '__main__':
#     func()
#
# """
# # input() 程序也是处于阻塞状态
# # requests.get(bilibili) 在网络请求返回数据之前, 程序也是处于阻塞状态的
# # 一般情况下, 当程序处于 IO操作的时候. 线程都会处于阻塞状态
#
# # 协程: 当程序遇见了IO操作的时候. 可以选择性的切换到其他任务上.
# # 在微观上是一个任务一个任务的进行切换. 切换条件一般就是IO操作
# # 在宏观上,我们能看到的其实是多个任务一起在执行
# # 多任务异步操作
#
# # 上方所讲的一切. 都是在单线程的条件下
# """


# python编写协程的程序
import asyncio
import time


# async def func():
#     print("你好啊, 我叫赛利亚")
#
#
# if __name__ == '__main__':
#     g = func()  # 此时的函数是异步协程函数. 此时函数执行得到的是一个协程对象
#     # print(g)
#     asyncio.run(g)  # 协程程序运行需要asyncio模块的支持


# async def func1():
#     print("你好啊, 我叫李诞")
#     # time.sleep(3)  # 当程序出现了同步操作的时候. 异步就中断了
#     await asyncio.sleep(3)  # 异步操作的代码
#     print("你好啊, 我叫李诞")
#
#
# async def func2():
#     print("你好啊, 我叫王建国")
#     # time.sleep(2)
#     await asyncio.sleep(2)
#     print("你好啊, 我叫王建国")
#
#
# async def func3():
#     print("你好啊, 我叫李雪琴")
#     await asyncio.sleep(4)
#     print("你好啊, 我叫李雪琴")
#
#
# if __name__ == '__main__':
#     f1 = func1()
#     f2 = func2()
#     f3 = func3()
#     tasks = [
#         f1, f2, f3
#     ]
#     t1 = time.time()
#     # 一次性启动多个任务(协程)
#     asyncio.run(asyncio.wait(tasks))
#     t2 = time.time()
#     print(t2 - t1)


# async def func1():
#     print("你好啊, 我叫李诞")
#     await asyncio.sleep(3)
#     print("你好啊, 我叫李诞")
#
#
# async def func2():
#     print("你好啊, 我叫王建国")
#     await asyncio.sleep(2)
#     print("你好啊, 我叫王建国")
#
#
# async def func3():
#     print("你好啊, 我叫李雪琴")
#     await asyncio.sleep(4)
#     print("你好啊, 我叫李雪琴")
#
#
# async def main():
#     # 第一种写法
#     # f1 = func1()
#     # await f1  # 一般await挂起操作放在协程对象前面
#     # 第二种写法(推荐)
#     tasks = [
#         asyncio.create_task(func1()),  # py3.8以后加上asyncio.create_task()
#         asyncio.create_task(func2()),
#         asyncio.create_task(func3())
#     ]
#     await asyncio.wait(tasks)
#
#
# if __name__ == '__main__':
#     t1 = time.time()
#     # 一次性启动多个任务(协程)
#     asyncio.run(main())
#     t2 = time.time()
#     print(t2 - t1)

# 在爬虫领域的应用
async def download(url):
    print("准备开始下载")
    await asyncio.sleep(2)  # 网络请求  requests.get()
    print("下载完成")


async def main():
    urls = [
        "http://www.baidu.com",
        "http://www.bilibili.com",
        "http://www.163.com"
    ]

    # 准备异步协程对象列表
    tasks = []
    for url in urls:
        d = asyncio.create_task(download(url))
        tasks.append(d)

    # tasks = [asyncio.create_task(download(url)) for url in urls]  # 这么干也行哦~

    # 一次性把所有任务都执行
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

总结

我们今天认识了协程和异步爬虫,一步步逐步认识了异步的优点,进一步提高了我们的程序效率。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

22. 协程与Python中的多任务异步协程 的相关文章

随机推荐

  • terminated 线程_Java中如何正确地中断一个线程?

    本文主要整理了关于线程中断的相关知识点 1 线程的状态 NEW 新建 一个尚未启动的线程处于这一状态 A thread that has not yet started is in this state RUNNABLE 可运行 一个正在
  • QT实现塔防游戏

    在基本功能实现后 对游戏进行优化 主要有以下几部分 实现传奇的升级 实现传奇的移除 绘画出波数与血量 实现游戏的暂停与回到选关关卡 实现游戏的胜利与失败 1 实现传奇的升级 创建一个selectbutton2类 ifndef SELECTB
  • 【华为OD机试真题 Python】多个数组合并

    前言 本专栏将持续更新华为OD机试题目 并进行详细的分析与解答 包含完整的代码实现 希望可以帮助到正在努力的你 关于OD机试流程 面经 面试指导等 如有任何疑问 欢迎联系我 wechat steven moda email nansun09
  • 如何让RecyclerView滑动到底部?

    在做这个功能时 使用scroll的任何一个方法 发现它每次都只滑到了一半 今天终于解决了 解决方法如下 LinearLayoutManager linearLayoutManager LinearLayoutManager recycler
  • 【批量注册组件】

    自动的批量注册组件 大致步骤 使用 require 提供的函数 context 加载某一个目录下的所有 vue 后缀的文件 然后 context 函数会返回一个导入函数 importFn 它又一个属性 keys 获取所有的文件路径 通过文件
  • 不得不读

    本次总共收集了8位大牛的8篇精品文章 内容涉及设计 验证 行业研究 ICer职场生活等各方面 欢迎大家点击阅读并关注 1 酒酒拿下四五十万的真实大厂面试经历 作者介绍 酒酒成电研三在读 自学算法leetcode刷题 双修IC验证 斩获互联网
  • 程序中难以捉摸的错误如何自动检测?Parasoft Insure++ v2021.1发布!

    Parasoft Insure 是用于 C 和 C 应用程序的自动化运行时应用程序测试工具 可检测难以捉摸的错误 例如内存损坏 内存泄漏 内存分配错误 变量初始化错误 变量定义冲突 指针错误 库错误 I O 错误 和逻辑错误 Parasof
  • Python网络爬虫使用教程

    文章目录 一 URL资源抓取 1 urllib 2 requests 3 requests html 二 正则表达式 三 数据解析 1 Beautiful Soup 2 lxml 3 selectolax 四 自动化爬虫selenium 五
  • 汉字的区码和位码

    写于2016年12月08日 汉字的区码和位码 由于国标码是四位十六进制 为了便于交流 大家常用的是四位十进制的区位码 所有的国标汉字与符号组成一个94 94的矩阵 在此方阵中 每一行称为一个 区 每一列称为一个 位 因此 这个方阵实际上组成
  • 字典树实现_数据结构与算法之字典树(Golang实现)

    1 字典树 算法描述 trie树的本质 就是利用字符串之间的公共前缀 将重复的前缀合并在一起 时间复杂度 构建O n 查询O k 1 1 1 算法步骤 根节点 什么都不表示 做一个字典比如a z 字母表 没一个节点包含这26个字母的字典表
  • 基于pytorch卷积人脸表情识别--毕业设计

    基于卷积神经网络的人脸表情识别 前言 毕业设计内容介绍 卷积神经网络的设计 卷积网络的模型 卷积池化过程详细说明 第一层卷积池化过程 第二层卷积池化过程 第三层卷积池化过程 全连接层过程 模型的训练过程 卷积与池化原理 模型如何训练 模型的
  • C++字符串全排列(递归法)和(迭代法)以及next_permutation底层原理详解

    目录 前言 next permutation的使用 实现全排列的两种算法 1 递归法 全排列方便理解记忆的方法 作为备用方法 实现代码 无重复元素情况 有重复元素情况 2 迭代法 next permutation底层原理 实现代码 有无重复
  • 【Makefile】Makefile 中 LINUXINCLUDE 的作用

    在驱动开发的时候 我们经常会创建很多新的头文件 这些头文件大多以 include xxx h 的形式放在源码中 而内核头文件则是以 include
  • Excel的基本操作(新手必看)

    11 文本记忆输入 先定义前两行单元格的文字 在下面的单元格中 右键 从下拉列表中选择 那么会显示前面输入的内容 12 数据的有效性验证 方式1 点击空白单元格 数据 数据验证 允许中选择序列 在来源中以 英文逗号的形式 添加 列表 方式2
  • spring boot 启动流程

    计时器开始计时 headless模式赋值 发送ApplicationAStartingEvent 配置环境模块 发送ApplicationEnvironmentPreparedEvent 打印banner 创建上下文应用对象 初始化失败分析
  • PEST分析顺丰服务需求_快递行业宏观环境分析

    快递行业宏观环境分析 宏观环境 PEST 分析 PEST 分析又称大环境分析 是研究宏观环境的有效工具 通过 Pest 分析法 公司能够剖析出自身所处的外部大环境究竟对自己的发 展是有利还是有害 以及据此作出战略规划 趋利避害 其中每一个
  • 如何处理日志文件丢失

    select group members from v log 查看日志文件的状态 select group status from v log 有状态来决定执行步骤 status active alter system checkpoin
  • ssh连接服务器协议错误,SSH服务 packet_write_wait: Connection to xxx port 22: Broken pipe错误处理...

    版权声明 本文为博主原创文章 遵循 CC 4 0 BY SA 版权协议 转载请附上原文出处链接和本声明 本文链接 http www one3 cn p 306 昨晚通过scp把其中一台服务器上面的文件传输到另一台机器上面 因为文件比较多且大
  • Rockchip CAN FD 开发文档

    Rockchip CAN FD 开发文档 前言 概述 产品版本 芯片名称 内核版本 RK356X 4 19 5 10 RK3588 5 10 目录 文章目录 Rockchip CAN FD 开发文档 toc CAN FD 驱动 驱动文件 D
  • 22. 协程与Python中的多任务异步协程

    目录 前言 协程概念 示例代码 Python编写协程程序 要用到的库函数 尝试编写异步 尝试改进函数 尝试优化代码 在爬虫领域的应用 总结 前言 本节我们介绍一个新概念 协程 协程顾名思义 是协助执行程序的过程 我们将介绍协程的概念和其在P