[python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程

文章目录

  • 前言
  • 1.python 生成器
    • 1.1 python 生成器概述
    • 1.2 关键字yield/yield from
    • 1.3 next/send函数
    • 1.4 StopInteration异常
    • 1.5 利用生成器实现生产者-消费者模型
    • 1.6 生成器和协程的关系
  • 2.生成器协程调度器
  • 3.python事件驱动编程
  • 4.实现协程调度器
  • 5.python 协程生态


前言

多进程和多线程在实际编程中用的已经非常多了,这篇文章的作用是记录下学习协程的心得体会,争取一篇文章搞定.


协程的好处不多说了,可以说是I/O密集型的利器.其实对于IO密集型任务我们还有一种选择就是协程。协程,又称微线程,英文名Coroutine,是运行在单线程中的“并发”,协程相比多线程的一大优势就是省去了多线程之间的切换开销,获得了更高的运行效率。Python中的异步IO模块asyncio就是基本的协程模块。
协程的切换不同于线程切换,是由程序自身控制的,没有切换的开销。协程不需要多线程的锁机制,因为都是在同一个线程中运行,所以没有同时访问数据的问题,执行效率比多线程高很多。

1.python 生成器

1.1 python 生成器概述

三个概念: 迭代/迭代器/可迭代对象
什么是生成器?
生成器也是一个可迭代对象

[num for num in range(10)]
(num for num in range(10) )

上面是一个列表,下面是一个生成器
在这里插入图片描述

生成器的特点

  1. 迭代完成一次,就指向最后一个了,再次迭代就迭代不出来了.和list不同.
    在这里插入图片描述
  2. 占用内存大小不同
import sys
sys.getsizeof(10)
sys.getsizeof("Hello World")
sys.getsizeof(l)
sys.getsizeof(gen)
gen = (num for num in range(10000))
l = [num for num in range(10000)]

sys.getsizeof(l)
sys.getsizeof(gen)

在这里插入图片描述
生成器几乎所占的内存不发生变化,而列表则随着元素的增多而增大!

生成器和列表非常类似,拥有一样的迭代方式,内存模型上更节省内存空间,生成器只能遍历一次。可以实现延时计算,并且用生成器代码更简洁。

1.2 关键字yield/yield from

Php, js,python C++ 中都有这个关键字,是一个比较高级的语法现象。
yield 只能再函数里面使用

def func():
	print("Hello World!");

type(func)输出 <class ‘function’>

def func():
	print("Hello World!");
	yield()

调用func()
在这里插入图片描述
注意 type(func()) 他返回了一个生成器!!!
在这里插入图片描述
普通函数 调用 返回None 是因为函数本来就返回none 就是普通函数的执行。
yield 就把一个函数变成了一个生成器。

def func():
	for i in range(10):
		print(i)


def func2():
	for i in range(10):
		yield i

在这里插入图片描述

for i in func2():
	print(i)

同样生成了0到9的10个数字。

1.3 next/send函数

多进程和多线程体现的是操作系统的能力,而协程体现的是程序员的流程控制能力。看下面的例子,甲,乙两个工人模拟两个工作任务交替进行,在单线程内实现了类似多线程的功能。

import time

def task1():
    while True:
        yield "<甲>也累了,让<乙>工作一会儿"
        time.sleep(1)
        print("<甲>工作了一段时间.....")


def task2(t):
    next(t)
    while True:
        print("-----------------------------------")
        print("<乙>工作了一段时间.....")
        time.sleep(2)
        print("<乙>累了,让<甲>工作一会儿....")
        ret = t.send(None)
        print(ret)
    t.close()

if __name__ == '__main__':
    t = task1()
    task2(t)

输出:

<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....
<乙>累了,让<甲>工作一会儿....
<甲>工作了一段时间.....
<甲>也累了,让<乙>工作一会儿
-----------------------------------
<乙>工作了一段时间.....

问题想一想为啥先执行乙的语句,再执行甲。
t = task1() 只是返回了一个生成器。task2(t) 的运行,next(t) 使得进入task1中执行,并且遇到yield停下,此时next(t)的返回值就是:<甲>也累了,让<乙>工作一会儿
然后进入task2的 while True, 直到遇到t.send(None), 执行权反转到task1,执行yield的下面一句。
这里的send可以不传none,
最早的时候,Python提供了yield关键字,用于制造生成器。也就是说,包含有yield的函数,都是一个生成器!
yield的语法规则是:在yield这里暂停函数的执行,并返回yield后面表达式的值(默认为None),直到被next()方法再次调用时,从上次暂停的yield代码处继续往下执行。
每个生成器都可以执行send()方法,为生成器内部的yield语句发送数据。此时yield语句不再只是yield xxxx的形式,还可以是var = yield xxxx的赋值形式。它同时具备两个功能,一是暂停并返回函数,二是接收外部send()方法发送过来的值,重新激活函数,并将这个值赋值给var变量!

def simple_coroutine():
    print('-> 启动协程')
    y = 10
    x = yield y
    print('-> 协程接收到了x的值:', x)

my_coro = simple_coroutine()
ret = next(my_coro)
print(ret)
my_coro.send(100)
-> 启动协程
10
-> 协程接收到了x的值: 1000
Traceback (most recent call last):
  File "c:\Users\jianming_ge\Desktop\碳汇算法第一版\carbon_code\单线程模拟多线程.py", line 54, in <module>
    my_coro.send(1000)
StopIteration

协程可以处于下面四个状态中的一个。当前状态可以导入inspect模块,使用inspect.getgeneratorstate(…) 方法查看,该方法会返回下述字符串中的一个。

‘GEN_CREATED’  等待开始执行。

‘GEN_RUNNING’  协程正在执行。

‘GEN_SUSPENDED’ 在yield表达式处暂停。

‘GEN_CLOSED’   执行结束。

因为send()方法的参数会成为暂停的yield表达式的值,所以,仅当协程处于暂停状态时才能调用 send()方法,例如my_coro.send(10)。不过,如果协程还没激活(状态是’GEN_CREATED’),就立即把None之外的值发给它,会出现TypeError。因此,始终要先调用next(my_coro)激活协程(也可以调用my_coro.send(None)),这一过程被称作预激活。

除了send()方法,其实还有throw()和close()方法:

generator.throw(exc_type[, exc_value[, traceback]])
使生成器在暂停的yield表达式处抛出指定的异常。如果生成器处理了抛出的异常,代码会向前执行到下一个yield表达式,而产出的值会成为调用generator.throw()方法得到的返回值。如果生成器没有处理抛出的异常,异常会向上冒泡,传到调用方的上下文中。

generator.close()
使生成器在暂停的yield表达式处抛出GeneratorExit异常。如果生成器没有处理这个异常,或者抛出了StopIteration异常(通常是指运行到结尾),调用方不会报错。如果收到GeneratorExit异常,生成器一定不能产出值,否则解释器会抛出RuntimeError异常。生成器抛出的其他异常会向上冒泡,传给调用方。

1.4 StopInteration异常

平时在进行python编程的时候,一般不关注异常,但学习生成器的时候,需要利用这个stopinteration进行编程

1.5 利用生成器实现生产者-消费者模型

1.6 生成器和协程的关系

2.生成器协程调度器

3.python事件驱动编程

4.实现协程调度器

5.python 协程生态

@asyncio.coroutine与yield from
@asyncio.coroutine:asyncio模块中的装饰器,用于将一个生成器声明为协程。
下面这段代码,我们创造了一个协程display_date(num, loop),然后它使用关键字yield from来等待协程asyncio.sleep(2)()的返回结果。而在这等待的2s之间它会让出CPU的执行权,直到asyncio.sleep(2)返回结果。asyncio.sleep(2)模拟的其实就是一个耗时2秒的IO读写操作。

import asyncio
import datetime

@asyncio.coroutine  # 声明一个协程
def display_date(num, loop):
    end_time = loop.time() + 10.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        yield from asyncio.sleep(2)  # 阻塞直到协程sleep(2)返回结果
loop = asyncio.get_event_loop()  # 获取一个event_loop
tasks = [display_date(1, loop), display_date(2, loop)]
loop.run_until_complete(asyncio.gather(*tasks))  # "阻塞"直到所有的tasks完成
loop.close()

Python3.5中对协程提供了更直接的支持,引入了async/await关键字。上面的代码可以这样改写:使用async代替@asyncio.coroutine,使用await代替yield from,代码变得更加简洁可读。从Python设计的角度来说,async/await让协程独立于生成器而存在,不再使用yield语法。

import asyncio
import datetime

async def display_date(num, loop):      # 注意这一行的写法
    end_time = loop.time() + 10.0
    while True:
        print("Loop: {} Time: {}".format(num, datetime.datetime.now()))
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(2)  # 阻塞直到协程sleep(2)返回结果

loop = asyncio.get_event_loop()  # 获取一个event_loop
tasks = [display_date(1, loop), display_date(2, loop)]
loop.run_until_complete(asyncio.gather(*tasks))  # "阻塞"直到所有的tasks完成
loop.close()

asyncio模块
asyncio的使用可分三步走:

创建事件循环
指定循环模式并运行
关闭循环
通常我们使用asyncio.get_event_loop()方法创建一个循环。

运行循环有两种方法:一是调用run_until_complete()方法,二是调用run_forever()方法。run_until_complete()内置add_done_callback回调函数,run_forever()则可以自定义add_done_callback(),具体差异请看下面两个例子。

使用run_until_complete()方法:

import asyncio

async def func(future):
    await asyncio.sleep(1)
    future.set_result('Future is done!')

if __name__ == '__main__':

    loop = asyncio.get_event_loop()
    future = asyncio.Future()
    asyncio.ensure_future(func(future))
    print(loop.is_running())   # 查看当前状态时循环是否已经启动
    loop.run_until_complete(future)
    print(future.result())
    loop.close()

使用run_forever()方法:

import asyncio

async def func(future):
    await asyncio.sleep(1)
    future.set_result('Future is done!')

def call_result(future):
    print(future.result())
    loop.stop()

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    future = asyncio.Future()
    asyncio.ensure_future(func(future))
    future.add_done_callback(call_result)        # 注意这行
    try:
        loop.run_forever()
    finally:
        loop.close()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/17541.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ShardingCore安装笔记

由于本人采用Visual Studio的nuget管理器安装ShardingCore经常出现网络错误的问题&#xff0c;所以采用离线包的方式安装插件。 nueget包下载地址&#xff1a;NuGet Gallery | ShardingCore 7.7.1.8 ShardingCore使用版本7.7.1.7 1、下载各种依赖文件&#xff0c;并存放到系…

JAVA11新特性

JAVA11新特性 概述 2018年9月26日,Oracle官方发布JAVA11.这是JAVA大版本周期变化后的第一个长期支持版本,非常值得关注.最新发布的JAVA11将带来ZGC HttpClient等重要特性,一共17个需要我们关注的JEP,参考文档http://openjdk.java.net/projects/jdk/11/ 181:基于嵌套的访问控制…

HashMap 底层原理///HashMap详解

HashMap超详细探讨 总述从集合出发探讨HashMapCollection接口继承树Map接口继承树 从Map接口来&#xff0c;一步一步深入其中Map接口概述Map接口&#xff1a;常用方法Map接口 Map实现类之一&#xff1a;HashMapHashMap的存储结构HashMap源码中的重要常量JDK 1.8之前JDK 1.8总结…

《花雕学AI》28:革命性的 ChatGPT for SEO——让您的排名飙升 50%!

引言&#xff1a; 如果您想写篇有吸引力的文章&#xff0c;或者您是一个博客和网站的拥有者&#xff0c;那么您一定知道 SEO&#xff08;搜索引擎优化&#xff09;的重要性。SEO 可以帮助您提高相应的流量、转化率和收入&#xff0c;但是 SEO 也是一个复杂和耗时的过程&#x…

S3C6410 中的 cascaded irqdomain 之 gpio

文章目录 VIC 中断 与 gpio 中断 的硬件拓扑图描述linux cascaded irq domainirq domain 初始化时获取 IRQ number(软件中断号) 时中断发生时如何调试linux irq domain 实例 VIC domain 与 gpio domain 的硬件拓扑语言描述VIC 与 INT_EINTx 的关系INT_EINTx 与 GPIO的关系INT_E…

数电中需要注意的问题

逻辑函数表达式之间的相互转换 &#xff08;更多请详见PPT&#xff09;若题目要求用&#xff1a; 与非门实现逻辑函数&#xff0c;则画卡诺图圈出值为1的然后化简 或非门实现逻辑函数&#xff0c;则画卡诺图圈出值为0的然后化简 与或非门实现逻辑函数&#xff0c;则画卡诺图圈…

c++ 构造函数与析构函数

c构造函数&#xff1a; (1)构造函数名必须与类名相同 (2)无返回值 (3)访问权限符一般设置为public (4)无自定义构造函数&#xff0c;编译器提供默认构造函数&#xff0c;构造函数只调用一次 c析构函数&#xff1a; (1)析构函数与类名相同&#xff0c;前面加~符号 (2)析构…

纯比例控制为什么会存在稳态误差,用纯增益系统举例

warning: 本文仅为个人思考&#xff0c;非常不严谨甚至可能会出现严重错误&#xff0c;请读者仔细甄别&#xff0c;若本文真的存在严重错误&#xff0c;恳请评论区纠正&#xff0c;我看到将会考虑修改或者删除文章 纯比例控制存在稳态误差是由其本质&#xff08;控制逻辑&#…

精炼计算机网络——物理层(一)

文章目录 前言2.1物理层的基本概念2.2 数据通信的基础知识2.2.1 数据通信系统的模型2.2.3 信道的极限容量 2.3 物理层下面的传输媒体2.3.1 导引型传输媒体2.3.2 非导引型传输媒体 总结 前言 经历了两篇文章的学习&#xff0c;相信读者们一定对计算机网络有了一个基础的了解。接…

一个新的ubuntu

1 安装cmake 方法一&#xff1a;现成的教程 ubuntu安装cmake_yuanzhoulvpi的博客-CSDN博客 方法二&#xff1a;自己总结的 安装openssl系统&#xff1a;sudo apt-get install libssl-dev 安装&#xff1a;sudo apt-get install build-essential 以上是防止安装cmake时缺少文…

PS磨皮插件portraiture最新版磨皮工具

Portraiture是一款智能磨皮插件&#xff0c;为Photoshop和Lightroom添加一键磨皮美化功能&#xff0c;快速对照片中皮肤、头发、眉毛等部位进行美化&#xff0c;无需手动调整&#xff0c;大大提高P图效率。全新4版本&#xff0c;升级AI算法&#xff0c;并独家支持多人及全身模式…

I2C工作流程

FM33A0XX的I2C接口只用作主机&#xff0c;且不支持多主机&#xff0c;因此挂在总线上的其他设备都是从机。总线上总是由主机提供同步时钟SCL&#xff0c;SDA数据流方向可以是主机发送从机接收&#xff0c;或者从机发送主机接收。 数据发送流程 1、主机发起 START 时序 2、主机…

C++之基础总结

目录 POD类型左值和右值静态全局变量(static)类型转换const/constexprconstconstexpr C中的关键字union基础知识点编译与函数参数入栈总结一些常见用法归纳&#xff1a; POD类型 平凡的和标准布局的——貌似和深度探索C对象模型中关于按位拷贝冲突 平凡的定义&#xff1a;符合…

Camtasia2023最好用的电脑屏幕录制软件

Camtasia2023是市场上最好的录像机和屏幕录制软件之一。强大的软件视频编辑程序的Camtasia 适用于Windows和iOS。 它支持多种流行的媒体格式&#xff0c;并对您创建的视频提供令人印象深刻的控制范围。3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&a…

文字的显示

文字的显示 文章目录 文字的显示1.文字编码方式2.英文和汉字的点阵显示3.显示中文“中”和“A”show_font.c结果 1.文字编码方式 数字>代表什么->显示为什么 GBK国标拓展 下列代码用不同编码方式保存utf-8.c ansi.c #include <stdio.h>int main(int argc ,char *…

网络编程之 Socket 套接字(使用数据报套接字和流套接字分别实现一个小程序(附源码))

文章目录 1. 什么是网络编程2. 网络编程中的基本概念1&#xff09;发送端和接收端2&#xff09;请求和响应3&#xff09;客户端和服务端4&#xff09;常见的客户端服务端模型 3. Socket 套接字1&#xff09;Socket 的分类2&#xff09;Java 数据报套接字通信模型3&#xff09;J…

基于Open3D的点云处理2-Open3D的IO与数据转换

三维数据类型 点云 某个坐标系下的点数据集&#xff0c;每个点包括三维坐标X&#xff0c;Y&#xff0c;Z、颜色、分类值、强度值、时间等信息&#xff1b; 储存格式&#xff1a;pts、LAS、PCD、xyz、asc、ply等&#xff1b;Mesh 多边形网格&#xff0c;常见的是三角网格&#…

有研究员公开了一个解析并提取 Dell PFS BIOS 固件的工具(上)

导语&#xff1a;研究员公开了一个解析并提取 Dell PFS BIOS 固件的工具。 Dell PFS BIOS提取器 介绍 解析 Dell PFS BIOS 映像并提取其 SPI/BIOS/UEFI 固件组件。它支持所有Dell PFS 修订版和格式&#xff0c;包括最初在 ThinOS 包中LZMA压缩、ZLIB压缩或拆分成块的格式。输出…

Kafka上的优化经验

1. 平滑扩容 Motivation kafka扩容⼀台新机器的流程 假如集群有 3 个 broker &#xff0c;⼀共有 4 个 TP &#xff0c;每个 3 副本&#xff0c;均匀分布。现在要扩容⼀台机器&#xff0c; 新 broker 加⼊集群后需要通过⼯具进⾏ TP 的迁移。⼀共迁移 3 个 TP 的副…

Spring Boot集成ShardingSphere实现按月数据分片及创建自定义分片算法 | Spring Cloud 44

一、前言 在前面我们通过以下章节对数据分片有了基础的了解&#xff1a; Spring Boot集成ShardingSphere实现数据分片&#xff08;一&#xff09; | Spring Cloud 40 Spring Boot集成ShardingSphere实现数据分片&#xff08;二&#xff09; | Spring Cloud 41 Spring Boot集…