第十二篇:多任务之协程(三)

  本篇主要介绍协程相关知识,但是在学习协程之前我们需要对迭代器和生成器做更加深入的了解,随后关于实现协程的方式进行了解,其中关于生成器、greenlet模块、gevent模块(重点),最后便是关于进程、线程、携程的总结。

一、迭代器

  关于迭代器已经在前面的文件中进行了介绍,但是分类是放在python的函数进阶中,在这里我们会纠正这个说法,详情请点击>>>

  可迭代对象和迭代器对象:

   可迭代对象:简单来说,可以被for循环遍历的对象即为可迭代对象,当然也可以说拥有__iter__()方法的对象即为可迭代对象;

   迭代器对象:迭代器对象是对可迭代对象进行迭代操作的对象,当然也可以说是同时拥有__iter__()方法和__next__()方法的对象即为迭代器对象;

故迭代器对象一定是可迭代对象,但是可迭代对象不一定是迭代器对象。

  接下来我们通过自己实现一个生成可迭代对象的模板:

import time
class MyIterable(object):
    """可迭代的对象"""
    def __init__(self):
        self.names = list()

    def add_name(self,name):
        self.names.append(name)

    def __iter__(self):
        # 可迭代对象当执行iter(obj)时,会自动运行__iter__()方法成为一个迭代器对象,同时需将本身传入。
        return MyIterator(self)  

class MyIterator(object):
    """迭代器对象"""
    def __init__(self,obj):  #
        self.obj = obj
        self.current_num = 0

    def __iter__(self):
        pass
    def __next__(self):
        """当调用next()方法,自动触发执行,返回可迭代对象中的值"""
        if self.current_num < len(self.obj.names):
            ret = self.obj.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            # 当抛出StopIteration异常时,for循环机制便会停止循环
            raise StopIteration

def main():
    myiterable = MyIterable()
    myiterable.add_name("alex")
    myiterable.add_name("James")
    myiterable.add_name("amanda")

    for name in myiterable:
        print(name)
        time.sleep(1)

if __name__ == "__main__":
    main()

  通过上述可知:

  迭代器对象一定有__iter__()方法,当调用iter(obj)将会返回一个迭代器对象,而通过该迭代器对可迭代对象进行操作,通过next()内置函数触发__next__()方法,返回可迭代对象中的值。


二、生成器

  在之前的随笔中我提到了生成器为一种特殊的函数,这里纠正一下:

  生成器为一类特殊的迭代器,而迭代器并且一个函数,而是一个具有__iter__()和__next__()方法的对象,同样生成器也具备这些。

 而实现生成器的方式有两种:

  一、通过列表生成式形成生成器:

gener = ( i*2 for i in range(10))  #通过列表生成式形成
print(next(gener))
print(next(gener))
print(gener)  # 输出结果为:<generator object <genexpr> at 0x0000026610BBDF68>

  通过打印print(gener)可以看出其为一个生成器对象,那么我们接下来看一下其方法:在终端输入:help(gener),得出:

  从输出结果可以看出:生成器对象也有__iter__和__next__()方法,故生成器为一个特殊的迭代器对象。

  

  二、通过在函数中调用yield语句

  首先我们来看一个实例,以斐波那契数列来举例:

#用普通函数实现的斐波那契数列
def feibonacil(num):
    a, b = 0, 1
    current_num = 0
    num_list = []
    while True:
        num_list.append(a)
        a, b = b,a+b
        if current_num > num:
            break
        current_num += 1
    return num_list

ret = feibonacil(10)
print(ret)

#输出结果为:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

#通过生成器实现斐波那契数列
def feibonacil(num):
    a, b = 0, 1
    current_num = 0
    num_list = []
    while True:
        yield a
        a, b = b,a+b
        if current_num > num:
            break
        current_num += 1

ret = feibonacil(10)
print(ret)

# 输出结果为:<generator object feibonacil at 0x000001C3594FDF68>
#通过next()可以不断获取生成器中的值

  注:只要在函数中引用了yield语句,则便不再是一个函数,而是一个生成器对象生成模板,当调用时获得返回值,其便是一个生成器对象。

  那么生成器是怎样执行的呢?我们可以进行测试一下:

# 测试迭代生成器对象的过程
def feibonacil(num):
    a, b = 0, 1
    current_num = 0
    num_list = []
    print("---test1----")
    while True:
        print("----test2----")
        yield a
        print("----test3-----")
        a, b = b,a+b
        if current_num > num:
            break
        current_num += 1

ret = feibonacil(3)
for i in ret:
    print(i)

#生成结果为:
---test1----
----test2----
0
----test3-----
----test2----
1
----test3-----
----test2----
1
----test3-----
----test2----
2
----test3-----
----test2----
3
----test3-----

测试迭代生成器对象的过程

 通过测试结果我们可以知道:

  当遍历到yield语句时,将会得到一个返回值并且停止在此处保持状态,得到下一次触发执行时,还会接着yield语句后面进行执行,直到遇到yield语句获得返回值,周而复始。

  故我们生成器遇到yield便停止运行且保持状态的特性,可以实现协程的效果,即当我们在这个生成器运行遇到yield暂停保持状态,运行下一个生成器直到遇到yield暂停保持状态,如此便能实现并发的效果。接下来看协程是如何实现的。


三、协程

  1、协程是什么?

  协程是Python中实现多任务的另外的一种方式,但是与线程、进程不同的是,它比线程占用的资源更小,且自带CPU上下文,即只要在合适的时机(阻塞或者优先级更高的),可以从一个协程切换到另外一个协程,而只要这个过程中保存或恢复 CPU上下文那么程序还是可以运行的。

  在简单了解协程之后,我们来了解几种实现协程的方式:

  一、协程 - yield

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import time
def worker1():
    """生成器worker1"""
    while True:
        print("---worker1---")
        yield
        time.sleep(1)

def worker2():
    """生成器worker2"""
    while True:
        print("---worker2---")
        yield
        time.sleep(1)

w1 = worker1()
w2 = worker2()

# 通过循环next()轮流触发生成器对象,实现并发的效果
for i in range(10):
    next(w1)
    next(w2)

  从上述案例中可以看出:由于生成器每次调用__next__()方法,均会执行到yield语句暂停保持状态,这样就可以切换两个函数使其来回执行,通过不断循环实现并发多任务的效果。

    二、协程 - greenlet

#!/usr/bin/env python
# -*- coding:utf-8 -*-
#coding=utf-8

from greenlet import greenlet  #导入greenlet模块
import time

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

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

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

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

#执行结果:
---A--
---B--
--C--

  解析:这里创建了两个greenlet协程对象,gr1和gr2,分别对应于函数test1()和test2()。使用greenlet对象的switch()方法,即可以切换协程。上例中,我们先调用”gr1.switch()”,函数test1()被执行,然后打印出”--A--″;接着由于”gr2.switch()”被调用,协程切换到函数test2(),打印出”--B--″;之后”gr1.switch()”又被调用,所以又切换到函数test1()。但注意,由于之前test1()已经执行到”gr2.switch()”,所以切换回来后会继续往下执行,也就是打印”--C--″;现在函数test1()退出,同时程序退出。由于再没有”gr2.switch()”来切换至函数test2(),所以程序”print("--D--")″不会被执行。

  从上面例子中可以看出:协程本质上是串行的 ,无法发挥多核CPU的优势。

    三、协程 - gevent

  其原理相当于:当程序运行到需要耗时操作或者处于阻塞的转态(比如I/O操作、网络、文件操作等耗时操作)时,通过gevent可以自动从这部分处于等待的转态的协程A,切换到另外一个已经处于就绪状态的协程B中执行,等到合适的时机协程A不再处于等待的转态时,在切换回来继续协程A的运行。

#---------------------gevent--------------------------------
import gevent
from gevent import monkey
import time

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

def worker1(name):
    for age in range(20,23):
        print("worker1[%s] is %s years old."%(name,str(age)))
        time.sleep(1)

def worker2(name):
    for age in range(30,33):
        print("worker2[%s] is %s years old."%(name,str(age)))
        time.sleep(1)

g1 = gevent.spawn(worker1,"alex")   # 创建协程对象g1,工作函数为worker1(产卵)
g2 = gevent.spawn(worker2,"wupeiqi")  # 创建协程对象g2,工作函数为worker2

gevent.joinall([g1,g2])  #协程对象g1,g2工作

#执行结果为:

worker1[alex] is 20 years old.
worker2[wupeiqi] is 30 years old.
worker1[alex] is 21 years old.
worker2[wupeiqi] is 31 years old.
worker1[alex] is 22 years old.
worker2[wupeiqi] is 32 years old.

  从上述例子中我们可以看出:

  通过gevent实现协程的过程和threading实现线程、multiprocessing实现进程的过程十分相似,通过gevent.spawn(work_func,"para1,para2",)创建协程对象,通过gevent.joinall(list(g_obj1,g_obj2))来触发协程对象进行工作,同时需注意的是:当有耗时操作时,需为程序打补丁 -----> monkey.pacth_all()来使其他模块的耗时操作转换为gevent直接实现的模块的耗时操作。

  


四、线程、进程、协程的区别及应用场景:

  

  • 有一个老板想要开个工厂进行生产某件商品(例如剪子):
  • 他需要花一些财力物力制作一条生产线,这个生产线上有很多的器件以及材料这些所有的 为了能够生产剪子而准备的资源称之为:进程
  • 只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步步的将剪子做出来,这个来做事情的工人称之为:线程
  • 这个老板为了提高生产率,想到3种办法:
    1. 在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即单进程 多线程方式
    2. 老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老板又花了些财力物力购置了另外一条生产线,然后再招些工人这样效率又再一步提高了,即多进程 多线程方式
    3. 老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是多进程的,每个进程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序 之后他才能再次工作) ,那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果一个线程等待某些条件,可以充分利用这个时间去做其它事情,其实这就是:协程方式

 请仔细理解如下的通俗描述

  总结: 

  1. 进程是资源分配的基本单位
  2. 进程是操作系统调度的基本的单位
  3. 进程切换需要的资源最大,效率最大,但是稳定性好,因为一个进程的结束不会影响另外一个进程 ---multiprocessing
  4. 线程切换需要的资源其次,效率一般,稳定性不如进程,切换效率不如协程 --threading
  5. 协程切换任务资源最小,切换效率高,当任务数量大,需要大量切换任务,协程更方便 --gevent
  6. 多进程、多线程可能实现并行,但是协程实在一个线程中运行的,一定为并发

 

原文地址:https://www.cnblogs.com/littlefivebolg/p/9311184.html

时间: 2024-10-16 05:03:05

第十二篇:多任务之协程(三)的相关文章

单线程多任务异步协程

目录  1. 概念讲解 2. 多任务异步协程理解 3.基于aiohttp模块异步网络请求实现数据爬取及数据解析 一.需要了解的概念 特殊函数:如果async修饰了一个函数的定义,那么该函数就变成了一个特殊函数, 特殊之处:特殊函数被调用后函数内部实现语句不会被立即执行 该函数调用之后会返回一个协程对象 协程对象:特殊函数调用后可以返回一个协程对象 协程 == 特殊函数 任务对象:对协程对象的进一步封装,就是一个高级协程对象 任务对象 == 协程对象 == 特殊的函数 绑定回调:task.add_

Python开发【第二十二篇】:Web框架之Django【进阶】

Python开发[第二十二篇]:Web框架之Django[进阶] 猛击这里:http://www.cnblogs.com/wupeiqi/articles/5246483.html 博客园 首页 新随笔 联系 订阅 管理 随笔-124  文章-127  评论-205 Python之路[第十七篇]:Django[进阶篇 ] Model 到目前为止,当我们的程序涉及到数据库相关操作时,我们一般都会这么搞: 创建数据库,设计表结构和字段 使用 MySQLdb 来连接数据库,并编写数据访问层代码 业务逻

SaltStack 学习笔记 - 第十二篇: SaltStack Web 界面

SaltStack 有自身的用python开发的web界面halite,好处是基于python,可以跟salt的api无缝配合,确定就比较明显,需要个性化对web界面进行定制的会比较麻烦,如果喜欢体验该界面的可以参考下面的文章  http://rfyiamcool.blog.51cto.com/1030776/1275443/ 我是运用另一个python+php来进行web开发,具体需要的工具有在我的另一篇文章里面介绍过,这里再重新进行整个开发介绍 首先介绍php 跟python通信的工具 pp

第二十二篇:再写Windows驱动,再玩Windbg---NET

2011年到现在,就没再怎么搞过Windows驱动了. 最近, 由于项目需要, 试着改一改一个显卡驱动(KMDOD), 从实践上证明, 我在理论上对一个驱动的架构的正确与否.(USB Display = KMDOD + AVStream). 其中, KMDOD是完成显示的部分功能, 完成其中的VidPN(Video present network), 将驱动中原来的POST物理设备转变为USB物理设备. 而AVStream之所以这样提出, 完成是由于USB Video class的启发, 要不然

SQL Server 索引的图形界面操作 &lt;第十二篇&gt;

一.索引的图形界面操作 SQL Server非常强大的就是图形界面操作.关于索引方面也一样那么强大,很多操作比如说重建索引啊,查看各种统计信息啊,都能够通过图形界面快速查看和操作,下面来看看SQL Server索引方面的GUI操作. 二.索引统计信息的图形界面操作 SQL Server 索引的图形界面操作 <第十二篇>

第二十二篇 信念

第二十二篇  信念 "信念"能带给一个人无穷的力量,这些力量可以支撑自己走过漫长的人生.一个人如果没有信念,就很难找到自己的人生方向,所以"信念"也可以理解为希望. 信念可以给到我们希望,也可以给到我们力量,所以一个人的信念会影响到自己的整个人生.当然信念也有好坏之分,好的信念能让自己积极向上.不畏艰难:坏的信念会让我们不思进取.随波逐流.这两种不同的信念会给到我们两种完全不同的人生,就看亲人们如何作出正确的选择. 一个人活在世上,可以选择走正确的人生道路,依靠好的

解剖SQLSERVER 第十二篇 OrcaMDF 行压缩支持(译)

解剖SQLSERVER 第十二篇   OrcaMDF 行压缩支持(译) http://improve.dk/orcamdf-row-compression-support/ 在这两个月的断断续续的开发工作中,我终于将OrcaMDF 压缩功能分支合并到主分支这意味着OrcaMDF 现在正式支持数据行压缩功能 支持的数据类型实现行压缩需要我修改几乎所有已实现的数据类型以将他们作为压缩存储.integer类型被压缩了,decimal类型变成可变长度,而可变长度类型基本上都被截断了进而用0来填补.所有先

(zt)Lua的多任务机制——协程(coroutine)

原帖:http://blog.csdn.net/soloist/article/details/329381 并发是现实世界的本质特征,而聪明的计算机科学家用来模拟并发的技术手段便是多任务机制.大致上有这么两种多任务技术,一种是抢占式多任务(preemptive multitasking),它让操作系统来决定何时执行哪个任务.另外一种就是协作式多任务(cooperative multitasking),它把决定权交给任务,让它们在自己认为合适的时候自愿放弃执行.这两种多任务方式各有优缺点,前者固

python学习[第十二篇] 数据类型之 集合

python学习[第十二篇] 数据类型之 集合 集合概念 python中集合是一组无序排列的哈希值.集合分为两种可变集合(set)和不可变集合(frozenset) 对可变集合可以修改和删除元素,对于不可变集合不允许.可变集合是不可以哈希的,因此既不能用作字典的键,也不能做其他集合的元素. 集合的增删改查 集合的创建于赋值 集合与列表([]) 和字典({})不同,集合没有特别的语法格式.列表和字典可以通过他们自己的工厂方法创建,这也是集合的唯一的创建方式.set()和frozenset() #创