python全栈(一)网络通信与服务器之多任务-协程

前言

本系列博文《网络通信与服务器》主要内容:udp、tcp、tcp下载文件案例、多任务、http协议、网络通信等 。
为了实现多任务,前面已经讲了进程和线程,为了更高效实现多任务,本博文将继续讲解一个新的实现多任务的方式-协程。

一、迭代器

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象,是一种可以被遍历的对象,并且能作用于next()函数。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不像列表,随时可以取后面的数据,也可以返回头取前面的数据。

1.可迭代对象

我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代。
但是,是否所有的数据类型都可以放到for…in…的语句中,然后让for…in…每次从中取出一条供我们使用,即供我们迭代呢?

验证是否可以迭代的对象的方式:
isinstance(classmate, Iterable)
验证是否是迭代器的方式:
isinstance(classmate_iterator, Iterator)

01-自己实现一个可以迭代的对象
eg1:

import time
from collections import Iterable
from collections import Iterator

class Classmate(object):  # 定义一个类
    def __init__(self):     # 定义属性
        self.names = list()  

    def add(self, name):    # 定义方法
        self.names.append(name)

    def __iter__(self):
        """如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法"""
        return ClassIterator()

class ClassIterator(object):
    def __iter__(self):
        pass

    def __next__(self):
        return 11

classmate = Classmate()    # 创建一个实例对象
classmate.add("老王")      # 这个对象调用一个方法
classmate.add("王二")
classmate.add("张三")

# print("判断classmate是否是可以迭代的对象:", isinstance(classmate, Iterable))
# classmate_iterator = iter(classmate)
# print("判断classmate_iterator是否是迭代器:", isinstance(classmate_iterator, Iterator))
# print(next(classmate_iterator))

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

在这里插入图片描述

示例讲解
只要类中有 def iter(self),就可以迭代。def iter(self)这个方法返回哪个对象的引用,for循环就调用哪个对象里面的next方法。
验证是否可迭代:
isinstance(classmate, Iterable)
iter(classmate)
for temp in classmate:
pass

如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法

eg2:

import time
from collections import Iterable
from collections import Iterator

class Classmate(object):
    def __init__(self):
        self.names = list()

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

    def __iter__(self):
        """如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法"""
        return ClassIterator(self)  # 有self,即返回此对象的引用的迭代器

class ClassIterator(object):

    def __init__(self, obj):
        self.obj = obj    # obj指代classmate的对象

    def __iter__(self):
        pass

    def __next__(self):
        return self.obj.names[0]

classmate = Classmate()
classmate.add("老王")
classmate.add("王二")
classmate.add("张三")

# print("判断classmate是否是可以迭代的对象:", isinstance(classmate, Iterable))
# classmate_iterator = iter(classmate)
# print("判断classmate_iterator是否是迭代器:", isinstance(classmate_iterator, Iterator))
# print(next(classmate_iterator))

for name in classmate:
    print(name)
    time.sleep(1)  # 为了取慢一点,好显示验证

视图分析上面的代码
在这里插入图片描述

eg3:

import time
from collections import Iterable
from collections import Iterator

class Classmate(object):
    def __init__(self):
        self.names = list()

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

    def __iter__(self):
        """如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法"""
        return ClassIterator(self)

class ClassIterator(object):

    def __init__(self, obj):
        self.obj = obj
        self.current_num = 0

    def __iter__(self):
        pass

    def __next__(self):
        ret = self.obj.names[self.current_num]
        self.current_num += 1
        return ret

classmate = Classmate()
classmate.add("老王")
classmate.add("王二")
classmate.add("张三")

# print("判断classmate是否是可以迭代的对象:", isinstance(classmate, Iterable))
# classmate_iterator = iter(classmate)
# print("判断classmate_iterator是否是迭代器:", isinstance(classmate_iterator, Iterator))
# print(next(classmate_iterator))

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

运行结果:
在这里插入图片描述
修改如eg4所示:
eg4:

import time
from collections import Iterable
from collections import Iterator

class Classmate(object):
    def __init__(self):
        self.names = list()

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

    def __iter__(self):
        """如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法"""
        return ClassIterator(self)

class ClassIterator(object):

    def __init__(self, obj):
        self.obj = obj
        self.current_num = 0

    def __iter__(self):
        pass

    def __next__(self):
        if self.current_num < len(self.obj.names):  # 和eg3的区别
            ret = self.obj.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration  # 告诉for循环结束

classmate = Classmate()
classmate.add("老王")
classmate.add("王二")
classmate.add("张三")

# print("判断classmate是否是可以迭代的对象:", isinstance(classmate, Iterable))
# classmate_iterator = iter(classmate)
# print("判断classmate_iterator是否是迭代器:", isinstance(classmate_iterator, Iterator))
# print(next(classmate_iterator))

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

总结

  1. iter 表明可以迭代,即可以使用for…in…循环遍历。既有 iter 又有 next 就表明是迭代器。用next()方法来遍历取值。
  2. 在next()方法中(__ next__),需要一个raise StopIteration 来告诉for循环结束

终结版代码
eg5:

import time
from collections import Iterable
from collections import Iterator

class Classmate(object):
    def __init__(self):
        self.names = list()
        self.current_num = 0

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

    def __iter__(self):
        """如果想要一个对象称为一个可以迭代的对象,即可以使用for,那么必须实现__iter__方法"""
        return self  # 调用iter(xxobj)的时候 只要__iter__方法返回一个迭代器即可,至于是自己 还是别的对象都可以的, 但是要保证是一个迭代器(即实现了 __iter__  __next__方法)

    def __next__(self):
        if self.current_num < len(self.names):
            ret = self.names[self.current_num]
            self.current_num += 1
            return ret
        else:
            raise StopIteration

classmate = Classmate()
classmate.add("老王")
classmate.add("王二")
classmate.add("张三")

# print("判断classmate是否是可以迭代的对象:", isinstance(classmate, Iterable))
# classmate_iterator = iter(classmate)
# print("判断classmate_iterator是否是迭代器:", isinstance(classmate_iterator, Iterator))
# print(next(classmate_iterator))

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

总结
调用iter(xxobj)的时候,只要__iter__方法返回一个迭代器即可,至于是自己还是别的对象都可以的, 但是要保证是一个迭代器(即实现了 iter __next__方法)

2. 迭代的应用

我们发现迭代器最核心的功能就是可以通过next()函数的调用来返回下一个数据值。如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定的规律计算生成的,那么也就意味着可以不用再依赖一个已有的 数据集合,也就是说不用再将所有的迭代的数据都一次性缓存下来供后续一次读取,这样可以节省大量的存储(内存)空间。

举个栗子,比如,数学中有个著名的斐波拉契数列(Fibonacci),数列中第一个数为0,第二个数为1,其后的每一个数都可以由前两个数相加得到:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …

现在我们想要通过for…in…循环来遍历迭代斐波拉契数列中的前n个数。那么这个斐波拉契数列我们就可以用迭代器来实现,每次迭代都通过数学计算来生成下一个数。

02-fibonacci
eg1:

nums = list()
a = 0 
b = 1
i = 0
while i < 10:
    nums.append(a)
    a, b = b, a+b
    i += 1

for num in nums:
    print(num)

eg2-迭代器

class Fibonacci(object):
    def __init__(self, all_num):
        self.all_num = all_num
        self.current_num = 0
        self.a = 0
        self.b = 1

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_num < self.all_num:
            ret = self.a
        
            self.a, self.b = self.b, self.a+self.b
            self.current_num += 1

            return ret
        else:
            raise StopIteration

fibo = Fibonacci(10)

for num in fibo:
    print(num)

列表现生成,会占据很大的内存。
python2.x: range(10)生成一个列表,返回一个值。xrang(10),返回是一个迭代的对象,随用随取,生成列表的方式,这就是迭代器。
python3.x: 中range(),取代了xrange()。迭代器返回的是生成这个数据的方式,而不是这一群数据的结果,所以就占据了很小的内存。
并不是只有for循环能接受可迭代对象,list、tuple等也能接受。

总结
通过迭代器能够完成数据的生成,而且不需要很大的存储空间;
列表的方式和迭代器的最核心的区别
列表中存储的是数据的结果,而迭代器中存储的是生成数据的方式。

二、生成器

1.生成器概述

有时候,序列或集合内的元素的个数非常巨大,如果全制造出来并放入内存,对计算机的压力是非常大的,这时候需要生成器来解决。
从Python2.2起,生成器提供了一种简洁的方式帮助返回列表元素的函数来完成简单和有效的代码。
生成器类似于返回值为数组的一个函数,这个函数可以接受参数,可以被调用,但是,不同于一般的函数会一次性返回包括了所有数值的数组,生成器一次只能产生一个值,这样消耗的内存数量将大大减小,而且允许调用函数可以很快的处理前几个返回值,因此生成器看起来像是一个函数,但是表现得却像是迭代器。它基于yield指令,允许暂停函数并立即返回结果,此函数保存其执行上下文,如果需要,可立即继续执行。
利用迭代器,我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简单的语法,即生成器(generator)。生成器是一类特殊的迭代器
生成器是一个特殊的程序,可以被用作控制循环的迭代行为,Python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,而可以使用next()函数和send()函数恢复生成器

2.创建生成器方法

两种方法

  • 方法1
    要创建一个生成器,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成()
    在这里插入图片描述
    创建L和G的区别仅在于最外层的[]和()。L是一个列表,而G是一个生成器。我们可以直接打印出列表L的每一个元素,而对于生成器G,偶们可以按照迭代器的使用方法来使用,即可以通过next()函数、for循环、list()等方法使用。
    在这里插入图片描述
  • 方法2
    如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
    generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数实现。
    注意:在用迭代器实现的方式中,我们要借助几个变量(n, current,num1,num2)来保存迭代的状态。现在我们用生成器来实现一下。

03-使用生成器完成feibonacii

def create_num(all_num):
    print("----1---")
    # a = 0
    # b = 1
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        print("----2---")
        # print(a) 将print(a) 换成yield a
        yield a  # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
        print("----3---")
        a, b = b, a+b
        current_num += 1
        print("----4---")

# 如果在调用create_num的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象
obj = create_num(10)

ret = next(obj)
print(ret)

ret = next(obj)
print(ret)

运行上面的代码结果为:在这里插入图片描述
在这里插入图片描述

def create_num(all_num):
    print("----1---")
    # a = 0
    # b = 1
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        print("----2---")
        # print(a) 将print(a) 换成yield a
        yield a  # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
        print("----3---")
        a, b = b, a+b
        current_num += 1
        print("----4---")

# 如果在调用create_num的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象
obj = create_num(10)

ret = next(obj)
print(ret)

ret = next(obj)
print(ret)

obj2 = create_num(2)

ret = next(obj2)
print(ret)

# for num in obj:
#    print(num)

运行结果:

----1---
----2---
0
----3---
----4---
----2---
1
----1---
----2---
0

04-生成器的研究

def create_num(all_num):
    print("----1---")
    # a = 0
    # b = 1
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        print("----2---")
        # print(a)
        yield a  # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
        print("----3---")
        a, b = b, a+b
        current_num += 1
        print("----4---")

# 如果在调用create_num的时候,发现这个函数中有yield那么此时,不是调用函数,而是创建一个生成器对象
obj = create_num(10)
obj2 = create_num(2)

ret = next(obj)
print("obj:", ret)

ret = next(obj)
print("obj:", ret)

ret = next(obj2)
print("obj2:", ret)

ret = next(obj)
print("obj:", ret)

ret = next(obj)
print("obj:", ret)
ret = next(obj)
print("obj:", ret)
# for num in obj:
#    print(num)

ret = next(obj2)
print("obj2:", ret)

ret = next(obj2)
print("obj2:", ret)

运行结果如下:
在这里插入图片描述
在这里插入图片描述
图二的处理方式如下:
05-通过异常判断生成器已经结束

def create_num(all_num):
    # a = 0
    # b = 1
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        # print(a)
        yield a  # 如果一个函数中有yield语句,那么这个就不在是函数,而是一个生成器的模板
        a, b = b, a+b
        current_num += 1
    return "ok...."  # 通过ret.value获取此返回值

obj2 = create_num(5)

while True:
    try:
        ret = next(obj2)
        print(ret)
    except Exception as ret:
        print(ret.value)  # ret.value就是生成器的返回值。
        break

运行结果:

0
1
1
2
3
ok....

显然,ok…在异常处理的except语句中获取到并打印出来。
send()方法有一个参数,该参数指定的是上一次被挂起的yield语句的返回值。

小结:

  • 使用了yield关键字的函数不再是函数,而是生成器。(使用了yield的函数就是生成器)
  • yield关键字有两点作用:
    1. 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
    2. 将yield关键字后面表达式的值作为返回值返回,此时可以理解为起到了return的作用
  • 可以使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
  • python3中的生成器可以使用return返回最终运行的返回值,而python2中的生成器不允许使用return
  • 返回一个返回值(即可以使用return从生成器中退出,但return后不能有任务表达式)
  • 通过异常判断生成器已经结束

3. 使用send唤醒

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

06-通过send来启动生成器
eg-v1:

def create_num(num):
    a, b = 0, 1
    current_num = 0
    while current_num < num:
        yield a
        a, b = b, a + b
        current_num += 1
    return 'ok....'

g = create_num(5)

print(g.send(None))
print(g.send('hello'))

运行结果

0
1

eg-v2:

def create_num(num):
    a, b = 0, 1
    current_num = 0
    while current_num < num:
        yield a
        a, b = b, a + b
        current_num += 1
    return "ok...."

g = create_num(5)

print(g.send('hahahha'))
print(g.send('world'))

运行结果:

Traceback (most recent call last):
  File "xxx/demo.py", line 14, in <module>
    print(g.send('hahahha'))
TypeError: can't send non-None value to a just-started generator

以上两代码分析解读
当第一次调用的是send()方法时,传入的参数只能是None,否则会报错,即第一次调用的不是next()时,那么调用send()的参数必须是None。换言之,首次由send(‘hahahha’)唤醒执行(即启动了生成器g),而且还带回来一个值“hahahha”, 由于首次执行,g里面没有yield a给它来赋值。

send()方法可以和next()方法结合使用,如上代码如下:
eg-v3-06-通过send来启动生成器终结版

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        ret = yield a
        print(">>>ret>>>>", ret)
        a, b = b, a+b
        current_num += 1

obj = create_num(10)

# obj.send(None)  # send一般不会放到第一次启动生成器,如果非要这样做 那么传递None

ret = next(obj)
print(ret)

# send里面的数据会传递给第5行,当做yield a的结果,然后ret保存这个结果,,, 
# send的结果是下一次调用yield时 yield后面的值
ret = obj.send("hahahha")  
print(ret)

运行结果如下:
在这里插入图片描述

def create_num(all_num):
    a, b = 0, 1
    current_num = 0
    while current_num < all_num:
        ret = yield a
        # print(">>>ret>>>>", ret)
        a, b = b, a+b
        current_num += 1

obj = create_num(10)

# obj.send(None)  # send一般不会放到第一次启动生成器,如果非要这样做 那么传递None

ret = next(obj)
print(ret)

# send里面的数据会传递给第5行,当做yield a的结果,然后ret保存这个结果,,, 
# send的结果是下一次调用yield时 yield后面的值
ret = obj.send("hahahha")  
print(ret)

运行结果如下:

0
1

解析
对比以上两段代,print(">>>ret>>>>", ret)此行注释掉,会发现不会在打印hahahha,说明一点send(“hahahha”)方法调用,带来的值(“hahahha”)只带进到迭代器obj中。

上例子eg-v3运行过程解读
生成器是一种特殊的迭代器,即也是迭代器,只要是迭代器,就可以用next来启动,首次由next(obj)唤醒执行(即启动了生成器obj),执行ret = yield a时,停在此处,create_num函数作用暂时保存,先执行yield a返回a的值,即打印出0,在调用send()方法时,进入obj,而且还带回来一个值“hahahha”,这时候,继续执行yield a后的代码“ret =”,把带回来的值“hahahha”赋给ret (也就是将“hahahha”赋值给整个yield a,即赋值给ret),继续向下执行,直到遇到yield a,把a返回第二次循环打印出1。(next()不可以传参数,send可以传参数。)
即:obj.next()等价obj.send(None)。

只要是迭代器,就可以用next来启动。
迭代器和生成器使用的都是代码。

三、协程-yield

1、协程概念

协程,又称微线程,是Python中另外一种实现多任务的方式,只不过是比线程占用(需要的资源)更小的执行单元。
Python中的协程大概经历了如下三个阶段:

  • 最初的生成器变形yield/send
  • yield from
  • 在最近的Python3.5版本中引入async/await关键字

协程自带CPU上下文,通过yield保存运行状态,才能恢复CPU上下文程序。

import time

def task_1():
    while True:
        print("---1----")
        time.sleep(0.1)
    
def task_2():
    while True:
        print("---2----")
        time.sleep(0.1)

def main():
    task_1()   # 调用的是函数
    task_2()
   
if __name__ == "__main__":
    main()

运行结果
在这里插入图片描述
解析
运行的还是单任务,非多任务。

2、协程-yield

07-使用yield完成多任务

import time

def task_1():
    while True:
        print("---1----")
        time.sleep(0.1)
        yield

def task_2():
    while True:
        print("---2----")
        time.sleep(0.1)
        yield

def main():
    t1 = task_1()  # 变成生成器对象
    t2 = task_2()
    # 先让t1运行一会,当t1中遇到yield的时候,再返回到24行,然后
    # 执行t2,当它遇到yield的时候,再次切换到t1中
    # 这样t1/t2/t1/t2的交替运行,最终实现了多任务....协程
    while True:
        next(t1)
        next(t2)
    
if __name__ == "__main__":
    main()

运行结果
在这里插入图片描述
结果分析
运行的是多任务,此代码实现的多任务是并发(任务交替执行),而非并行,即假的多任务。
注意点
进程间切换任务,消耗资源非常大,即创建进程和销毁进程消耗资源非常大。进程没有线程效率高,但协程效率更高。协程调用一个任务就像调用一个函数。

3、协程-greenlet

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

安装方式

sudo pip3 install greenlet

用到函数

from greenlet import greenlet

gr1 = greenlet(function)
gr1.switch()  

08-使用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()

ctrl+c终止程序。
运行效果

"---A--"
"---B--"
"---A--"
"---B--"
"---A--"
"---B--"

易知,协程利用程序的IO来切换任务,用greenlet模块需要人工手动切换。

IO扩展

  • 同步
    是指代码调用IO操作时,必须等待IO操作完成才返回的调用方式。
  • 异步
    是指代码调用IO操作时,不必等IO操作完成就返回的调用方式。
    同步异步比较如下:
    在这里插入图片描述

4、协程-gevent

终结版,也就是最终协程使用的是gevent

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

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

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

安装

pip3 install gevent
import gevent

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

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

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

print("----1---")       
g1 = gevent.spawn(f1, 5)  # 创建成功不会执行,在g1.join()开始执行
print("----2---")
g2 = gevent.spawn(f2, 5)
print("----3---")
g3 = gevent.spawn(f3, 5)
print("----4---")
g1.join()  # 开始执行g1.等待g1执行完,单线程卡在此处
g2.join()
g3.join()

ctrl+C终止程序。

运行结果:
在这里插入图片描述
未达到预期的效果实现多任务,在代码中添加time.sleep(0.5)再次验证一下,代码如下:

import gevent
import time

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

print("----1---")
g1 = gevent.spawn(f1, 5)
print("----2---")
g2 = gevent.spawn(f2, 5)
print("----3---")
g3 = gevent.spawn(f3, 5)
print("----4---")
g1.join()
g2.join()
g3.join()

运行结果:
在这里插入图片描述
分析
添加了time.sleep(0.5)来验证一下,看结果一样,显然未达到预期的效果实现多任务。

09-使用gevent实现多任务
将上述time.sleep(0.5)换成gevent.sleep(0.5)

import gevent
import time

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # time.sleep(0.5)
        gevent.sleep(0.5)

def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # time.sleep(0.5)
        gevent.sleep(0.5)

def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        # time.sleep(0.5)
        gevent.sleep(0.5)

print("----1---")
g1 = gevent.spawn(f1, 5)
print("----2---")
g2 = gevent.spawn(f2, 5)
print("----3---")
g3 = gevent.spawn(f3, 5)
print("----4---")
g1.join()
g2.join()
g3.join()

运行结果:
在这里插入图片描述
分析:
遇到延迟,自己切换其他对象
一个线程只能执行一个任务。协程依赖于线程,线程依赖于进程。

将程序中用到的耗时操作的代码,换成gevent中自己实现的模块代码如下:
10-gevent打补丁

import gevent
import time
from gevent import monkey

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

def f1(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

def f2(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

def f3(n):
    for i in range(n):
        print(gevent.getcurrent(), i)
        time.sleep(0.5)

print("----1---")
g1 = gevent.spawn(f1, 5)
print("----2---")
g2 = gevent.spawn(f2, 5)
print("----3---")
g3 = gevent.spawn(f3, 5)
print("----4---")
g1.join()
g2.join()
g3.join()

运行结果如下:
在这里插入图片描述
结果和上一个代码结果一样。
更简洁的办法,也是终结版

import gevent
import time
import random
from gevent import monkey

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

def coroutime_work(coroutime_name):
    for i in range(10):
        print(coroutime_name, i)
        time.sleep(random.random())

gevent.joinall([
    gevent.spawn(coroutime_work, 'work1'),
    gevent.spawn(coroutime_work, 'work2'),
])

运行结果:
在这里插入图片描述

四、应用:并发下载器

11-downloader

import urllib.request  
import gevent
from gevent import monkey

monkey.patch_all()  # 打个补丁

def downloader(img_name, img_url):
	req = urllib.request.urlopen(img_url)

	img_content = req.read()  # img_content网站的源码

	with open(img_name, "wb") as f:
		f.write(img_content)

def main():
# 开两个协程
	gevent.joinall([
	        gevent.spawn(downloader, "3.jpg", "https://rpic.douyucdn.cn/appCovers/2017/09/22/1760931_20170922133718_big.jpg"),
	        gevent.spawn(downloader, "4.jpg", "https://rpic.douyucdn.cn/appCovers/2017/09/17/2308890_20170917232900_big.jpg")
	])


if __name__ == '__main__':
	main()

代码中联网的方式:
import urllib.request
req = urllib.request.urlopen(url)
url是一个网址。

总结

进程、线程、协程区别(重点)
在这里插入图片描述
简单总结

  1. 进程是资源分配的单位;
  2. 线程是操作系统调度的单位;
  3. 进程切换需要的资源很大、效率很低;
  4. 线程切换需要的资源一般、效率一般(在不考虑GIL的情况下);
  5. 协程切换任务资源很小、效率高;
  6. 多进程、多线程根据CPU核数不同可能是并行的,但是协程是在一个线程中,所以是并发。
  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值