Python学习之路-多任务:协程

这篇具有很好参考价值的文章主要介绍了Python学习之路-多任务:协程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Python学习之路-多任务:协程

简介

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

通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外一个函数中执行,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者自己确定

协程和线程差异

在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。

迭代

迭代是访问集合元素的一种方式。使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。

迭代器

迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

可迭代对象

把可以通过for…in…这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象(Iterable)。可以使用 isinstance() 判断一个对象是否是可迭代对象 Iterable 对象。

可迭代对象的本质

我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在for…in…中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)。

可迭代对象的本质就是可以向我们提供一个这样的中间“人”即迭代器帮助我们对其进行迭代遍历使用。

可迭代对象通过__iter__方法向我们提供一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据.

那么也就是说,一个具备了__iter__方法的对象,就是一个可迭代对象。

{{< admonition warning “注意” true >}}

可以通过iter()函数获取这些可迭代对象的迭代器(iter()函数实际上就是调用了可迭代对象的__iter__方法),然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据(使用next()函数的时候,调用的就是迭代器对象的__next__方法)。当我们已经迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常,来告诉我们所有数据都已迭代完成,不用再执行next()函数了。

{{< /admonition >}}

for…in…循环的本质

for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束。

生成器

简介

生成器是一类特殊的迭代器。利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)

创建生成器方法

  • 只要把一个列表生成式的 [ ] 改成 ( ),如下

    In [1]: A = [ x*2 for x in range(5)]
    
    In [2]: A
    Out[2]: [0, 2, 4, 6, 8]
    
    In [3]: B = ( x*2 for x in range(5))
    
    In [4]: B
    Out[4]: <generator object <genexpr> at 0x7f626c132db0>
    

    创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是一个列表,而 G 是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,我们可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用。

    In [5]: next(G)
    Out[5]: 0
    
    In [6]: next(G)
    Out[6]: 2
    
    In [7]: next(G)
    Out[7]: 4
    
    In [8]: next(G)
    Out[8]: 6
    
    In [9]: next(G)
    Out[9]: 8
    
    In [10]: next(G)
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-24-380e167d6934> in <module>()
    ----> 1 next(G)
    
    StopIteration:
      
    In [11]:
    In [12]: G = ( x*2 for x in range(5))
    
    In [13]: for x in G:
       ....:     print(x)
       ....:     
    0
    2
    4
    6
    8
    
    In [14]:
    
  • 将原本在迭代器__next__方法中实现的基本逻辑放到一个函数中来实现,但是将每次迭代返回数值的return换成了yield,此时新定义的函数便不再是函数,而是一个生成器了。简单来说:只要在def中有yield关键字的 就称为 生成器。用生成器实现斐波那契数列

    In [15]: def fib(n):
       ....:     current = 0
       ....:     num1, num2 = 0, 1
       ....:     while current < n:
       ....:         num = num1
       ....:         num1, num2 = num2, num1+num2
       ....:         current += 1
       ....:         yield num
       ....:     return 'done'
       ....:
    
    In [16]: F = fib(5)
    
    In [17]: next(F)
    Out[17]: 1
    
    In [18]: next(F)
    Out[18]: 1
    
    In [19]: next(F)
    Out[19]: 2
    
    In [20]: next(F)
    Out[20]: 3
    
    In [21]: next(F)
    Out[21]: 5
    
    In [22]: next(F)
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-22-8c2b02b4361a> in <module>()
    ----> 1 next(F)
    
    StopIteration: done
    

    此时按照调用函数的方式( 案例中为F = fib(5) )使用生成器就不再是执行函数体了,而是会返回一个生成器对象( 案例中为F ),然后就可以按照使用迭代器的方式来使用生成器了。

    In [23]: for n in fib(5):
       ....:     print(n)
       ....:     
    1
    1
    2
    3
    5
    

    但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

    In [25]: g = fib(5)
    
    In [26]: while True:
       ....:     try:
       ....:         x = next(g)
       ....:         print("value:%d"%x)      
       ....:     except StopIteration as e:
       ....:         print("生成器返回值:%s"%e.value)
       ....:         break
       ....:     
    value:1
    value:1
    value:2
    value:3
    value:5
    生成器返回值:done
    

使用send唤醒

我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。

例子:执行到yield时,gen函数作用暂时保存,返回i的值; temp接收下次c.send(“python”),send发送过来的值,c.next()等价c.send(None)

In [27]: def gen():
   ....:     i = 0
   ....:     while i<5:
   ....:         temp = yield i
   ....:         print(temp)
   ....:         i+=1
   ....:

使用send

In [28]: f = gen()

In [29]: next(f)
Out[29]: 0

In [30]: f.send('haha')
haha
Out[30]: 1

In [31]: next(f)
None
Out[31]: 2

In [32]: f.send('haha')
haha
Out[32]: 3

使用next函数

In [33]: f = gen()

In [34]: next(f)
Out[34]: 0

In [35]: next(f)
None
Out[35]: 1

In [36]: next(f)
None
Out[36]: 2

In [37]: next(f)
None
Out[37]: 3

In [38]: next(f)
None
Out[38]: 4

In [39]: next(f)
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-17-468f0afdf1b9> in <module>()
----> 1 next(f)

StopIteration:

使用__next__()方法(不常使用)

In [40]: f = gen()

In [41]: f.__next__()
Out[41]: 0

In [42]: f.__next__()
None
Out[42]: 1

In [43]: f.__next__()
None
Out[43]: 2

In [44]: f.__next__()
None
Out[44]: 3

In [45]: f.__next__()
None
Out[45]: 4

In [46]: f.__next__()
None
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-24-39ec527346a9> in <module>()
----> 1 f.__next__()

StopIteration:

简单实现协程

import time

def work1():
    while True:
        print("----work1---")
        yield
        time.sleep(0.5)

def work2():
    while True:
        print("----work2---")
        yield
        time.sleep(0.5)

def main():
    w1 = work1()
    w2 = work2()
    while True:
        next(w1)
        next(w2)

if __name__ == "__main__":
    main()

运行结果:

----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
----work1---
----work2---
...省略...

greenlet

为了更好使用协程来完成多任务,python中的greenlet模块对其封装,从而使得切换任务变的更加简单

安装

使用如下命令安装greenlet模块:

sudo pip3 install greenlet

简单实现

from greenlet import greenlet
import time

def test1():
    while True:
        print "---A--"
        gr2.switch()
        time.sleep(0.5)

def test2():
    while True:
        print "---B--"
        gr1.switch()
        time.sleep(0.5)

gr1 = greenlet(test1)
gr2 = greenlet(test2)

#切换到gr1中运行
gr1.switch()

运行效果:

---A--
---B--
---A--
---B--
---A--
---B--
---A--
---B--
...省略...

gevent

greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent

其原理是当一个greenlet遇到IO(指的是input output 输入输出,比如网络、文件操作等)操作时,比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行。

由于IO操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在运行,而不是等待IO

安装

使用如下命令安装gevent模块:

pip3 install gevent

简单实现

import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

运行结果

<Greenlet at 0x10e49f550: f(5)> 0
<Greenlet at 0x10e49f550: f(5)> 1
<Greenlet at 0x10e49f550: f(5)> 2
<Greenlet at 0x10e49f550: f(5)> 3
<Greenlet at 0x10e49f550: f(5)> 4
<Greenlet at 0x10e49f910: f(5)> 0
<Greenlet at 0x10e49f910: f(5)> 1
<Greenlet at 0x10e49f910: f(5)> 2
<Greenlet at 0x10e49f910: f(5)> 3
<Greenlet at 0x10e49f910: f(5)> 4
<Greenlet at 0x10e49f4b0: f(5)> 0
<Greenlet at 0x10e49f4b0: f(5)> 1
<Greenlet at 0x10e49f4b0: f(5)> 2
<Greenlet at 0x10e49f4b0: f(5)> 3
<Greenlet at 0x10e49f4b0: f(5)> 4

可以看到,3个greenlet是依次运行而不是交替运行

切换执行

import gevent

def f(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        #用来模拟一个耗时操作,注意不是time模块中的sleep
        gevent.sleep(1)

g1 = gevent.spawn(f, 5)
g2 = gevent.spawn(f, 5)
g3 = gevent.spawn(f, 5)
g1.join()
g2.join()
g3.join()

运行结果

<Greenlet at 0x7fa70ffa1c30: f(5)> 0
<Greenlet at 0x7fa70ffa1870: f(5)> 0
<Greenlet at 0x7fa70ffa1eb0: f(5)> 0
<Greenlet at 0x7fa70ffa1c30: f(5)> 1
<Greenlet at 0x7fa70ffa1870: f(5)> 1
<Greenlet at 0x7fa70ffa1eb0: f(5)> 1
<Greenlet at 0x7fa70ffa1c30: f(5)> 2
<Greenlet at 0x7fa70ffa1870: f(5)> 2
<Greenlet at 0x7fa70ffa1eb0: f(5)> 2
<Greenlet at 0x7fa70ffa1c30: f(5)> 3
<Greenlet at 0x7fa70ffa1870: f(5)> 3
<Greenlet at 0x7fa70ffa1eb0: f(5)> 3
<Greenlet at 0x7fa70ffa1c30: f(5)> 4
<Greenlet at 0x7fa70ffa1870: f(5)> 4
<Greenlet at 0x7fa70ffa1eb0: f(5)> 4

给程序打补丁

from gevent import monkey
import gevent
import random
import time

def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())

gevent.joinall([
        gevent.spawn(coroutine_work, "work1"),
        gevent.spawn(coroutine_work, "work2")
])

运行结果

work1 0
work1 1
work1 2
work1 3
work1 4
work1 5
work1 6
work1 7
work1 8
work1 9
work2 0
work2 1
work2 2
work2 3
work2 4
work2 5
work2 6
work2 7
work2 8
work2 9

monkey

from gevent import monkey
import gevent
import random
import time

# 有耗时操作时需要
monkey.patch_all()  # 将程序中用到的耗时操作的代码,换为gevent中自己实现的模块

def coroutine_work(coroutine_name):
    for i in range(10):
        print(coroutine_name, i)
        time.sleep(random.random())

gevent.joinall([
        gevent.spawn(coroutine_work, "work1"),
        gevent.spawn(coroutine_work, "work2")
])

运行结果文章来源地址https://www.toymoban.com/news/detail-797584.html

work1 0
work2 0
work1 1
work1 2
work1 3
work2 1
work1 4
work2 2
work1 5
work2 3
work1 6
work1 7
work1 8
work2 4
work2 5
work1 9
work2 6
work2 7
work2 8
work2 9

进程、线程、协程对比

  1. 进程是资源分配的单位
  2. 线程是操作系统调度的单位
  3. 进程切换需要的资源很最大,效率很低
  4. 线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
  5. 协程切换任务资源很小,效率高
  6. 多进程、多线程根据cpu核数不一样可能是并行的,但是协程是在一个线程中 所以是并发

到了这里,关于Python学习之路-多任务:协程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Python学习之路-函数进阶

    函数根据有没有参数以及有没有返回值,可以相互组合,一共有4 种组合形式:无参数,无返回值;无参数,有返回值;有参数,无返回值;有参数,有返回值。 {{ admonition tip “提示” true }} 定义函数时,是否接收参数,或者是否返回结果,是根据实际的功能需求来决定的!

    2024年01月23日
    浏览(43)
  • Python学习之路-编码风格

    Python的设计哲学是“优雅”、“明确”、“简单”。它的重要准则被称为“Python之禅”。 Python之禅 又名PEP 20,在Python解释器内运行 import this 可以获得完整的列表,下面是我的翻译与解读: Python开发的哲学是“用一种方法,最好是只有一种方法来做一件事”。在设计Python程序

    2024年01月20日
    浏览(67)
  • Python学习之路-内存管理

    Python的内存管理机制可以总结为:引用计数、垃圾回收、内存池。 引用计数是一种非常高效的内存管理手段, 当一个 Python 对象被引用时其引用计数增加 1, 当其不再被一个变量引用时则计数减 1. 当引用计数等于 0 时对象被删除。 引用计数 引用计数也是一种垃圾收集机制,

    2024年01月21日
    浏览(94)
  • Python学习之路-初识面向对象

    面向对象编程(英语:Object-oriented programming,缩写:OOP)是种具有对象概念的编程典范,同时也是一种程序开发的抽象方针。它将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性,对象里的程序可以访问及经常修改对象相关连的数据。

    2024年01月21日
    浏览(56)
  • Python学习之路-模块和包

    简介 模块是 Python 程序架构的一个核心概念。每一个以扩展名 py 结尾的 Python 源代码文件都是一个模块,模块名同样也是一个标识符,需要符合标识符的命名规则。在模块中定义的全局变量、函数、类都是提供给外界直接使用的工具。模块就好比是工具包,要想使用这个工具

    2024年02月01日
    浏览(44)
  • Python学习之路-正则表达式

    正则表达式是计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。 在Python中需要通过正则表达式对字符串进行匹配的时候,可以使用一个模块

    2024年01月24日
    浏览(63)
  • Python学习之路-爬虫提高:selenium

    Selenium是一个Web的自动化测试工具,最初是为网站自动化测试而开发的,Selenium 可以直接运行在浏览器上,它支持所有主流的浏览器(包括PhantomJS这些无界面的浏览器),可以接收指令,让浏览器自动加载页面,获取需要的数据,甚至页面截屏 PhantomJS 是一个基于Webkit的“无界

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

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

    2024年02月09日
    浏览(44)
  • Python 学习之路:python3中pygame解决中文显示

            这篇文章主要介绍python3中pygame解决中文显示问题,通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下。   1.实例代码展示: 2.实例运行效果: 3.路径和字体设置:         需要自己下载好字体,放置一个指定

    2024年02月11日
    浏览(42)
  • Python学习之路-面向对象:三个基本特征

    封装:根据职责将属性和方法封装到一个抽象的类中 继承:实现代码的重用,相同的代码不需要重复的编写 多态:不同的对象调用相同的方法,产生不同的执行结果,增加代码的灵活度 封装是面向对象编程的一大特点,面向对象编程的第一步就是将属性和方法封装到一个抽

    2024年02月02日
    浏览(58)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包