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

这篇具有很好参考价值的文章主要介绍了[python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。


前言

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


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

1.python 生成器

1.1 python 生成器概述

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

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

上面是一个列表,下面是一个生成器
[python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程

生成器的特点

  1. 迭代完成一次,就指向最后一个了,再次迭代就迭代不出来了.和list不同.
    [python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程
  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)

[python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程
生成器几乎所占的内存不发生变化,而列表则随着元素的增多而增大!

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

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()
[python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程
注意 type(func()) 他返回了一个生成器!!!
[python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程
普通函数 调用 返回None 是因为函数本来就返回none 就是普通函数的执行。
yield 就把一个函数变成了一个生成器。

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


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

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

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 生成器和协程的关系

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

生成器通过yield可以主动让出cpu
多个生成器可以共同使用cpu,共同协作,有序进行
生成器可以进一步封装成协程

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()方法:文章来源地址https://www.toymoban.com/news/detail-433386.html

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()

到了这里,关于[python] 协程学习从0到1,配合案例,彻底理解协程,耗费资源不增加,效果接近多线程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【深度学习笔记】彻底理解torch中的tensor与numpy中array区别及用法

    刚接触深度学习的同学,很多开源项目代码中, 张量tensor 与 数组array 都有使用,不清楚两者有什么区别,以及怎么使用,如何相互转换等。博主起初也有类似的疑惑,经过查阅资料以及实践,逐渐有了深入了解,本文将记录并分享自己对两者的理解,可供参考。 提示:以下

    2023年04月08日
    浏览(48)
  • Python学习之路-多任务:协程

    协程,又称微线程,纤程。英文名Coroutine。协程是python个中另外一种实现多任务的方式,只不过比线程更小占用更小执行单元(理解为需要的资源)。 为啥说它是一个执行单元,因为它自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这

    2024年01月17日
    浏览(40)
  • 某省公共资源交易电子公共服务平台学习案例

    声明:本文仅限学习交流使用,禁止用于非法用途、商业活动等。否则后果自负。如有侵权,请告知删除,谢谢!本教程也没有专门针对某个网站而编写,单纯的技术研究 目标案例:aHR0cHM6Ly9nZ3p5ZncuZnVqaWFuLmdvdi5jbi9idXNpbmVzcy9saXN0Lw== 1.对应接口 2.对应难点 参数加密 数据加密 通过

    2024年02月08日
    浏览(31)
  • 从零开始学习iftop流量监控(找出服务器耗费流量最多的ip和端口)

    iftop是类似于top的实时流量监控工具。 作用:监控网卡的实时流量(可以指定网段)、反向解析IP、显示端口信息等 官网: http://www.ex-parrot.com/~pdw/iftop/ 一般参数 主机参数 端口显示参数 输出排序参数 1.显示网卡eth0的信息,主机通过ip显示 2.显示端口号(添加-P参数,进入界面

    2023年04月08日
    浏览(37)
  • 深入理解 Golang: Goroutine 协程

    进程用来分配内存空间,是操作系统分配资源的最小单位;线程用来分配 CPU 时间,多个线程共享内存空间,是操作系统或 CPU 调度的最小单位;协程用来精细利用线程。协程就是将一段程序的运行状态打包,可以在线程之间调度。或者说将一段生产流程打包,使流程不固定在

    2024年02月11日
    浏览(65)
  • 最新完整热更新实战案例学习,包括资源热更新及代码热更新【文末送书】

    🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN 🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏制作专栏推荐:游戏制作 🌲Unity实战100例专栏推荐:Unity 实战100例 教程 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📆 未来很长

    2023年04月08日
    浏览(23)
  • go的协程和管道运用案例

    2024年01月19日
    浏览(29)
  • Python 华为面试手撕代码 + 八股文,机器学习参数调节,损失函数,激活函数,线程、进程和协程

    一、手撕代码:力扣原题905 二、八股文部分:有点紧张,忘了好多东西 1.深度学习模型优化的方法有哪些? 深度学习模型的优化策略包括以下几个方面: (1)选择合适的激活函数:激活函数对模型的表达能力和收敛速度有很大影响,常用的激活函数包括ReLU、Sigmoid、Tanh等。

    2024年02月09日
    浏览(32)
  • 主成分分析法(PCA)的理解(附python代码案例)

    最近在文献调研,发现PCA基本都有用到,回忆起了机器学习和数学建模,总之还是要好好学学捏。 定义 :主成分分析(Principal Component Analysis, PCA)是一种统计方法。通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分。 换一

    2024年02月03日
    浏览(31)
  • Unity 热更新技术 |(七)完整热更新实战案例学习使用,包括资源热更新及代码热更新【文末书】

    🎬 博客主页:https://xiaoy.blog.csdn.net 🎥 本文由 呆呆敲代码的小Y 原创,首发于 CSDN 🙉 🎄 学习专栏推荐:Unity系统学习专栏 🌲 游戏制作专栏推荐:游戏制作 🌲Unity实战100例专栏推荐:Unity 实战100例 教程 🏅 欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正! 📆 未来很长

    2023年04月17日
    浏览(24)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包