多任务之协程
协程
- 1什么是协程?
- 我们要知道协程需要从头开始
如果你执行用协程,其他得不用了解直接用gevent。但是你要想明白原理,那你要慢慢从头了解
迭代器
- 1 判断一个变量是不是可以迭代
from collections import Iterable
isinstance([11,22,33], Iterable)判断是不是Itertable的子类
解释一下type和isinstance的区别:
isinstance() 与 type() 区别:
type() 不会认为子类是一种父类类型,不考虑继承关系。
isinstance() 会认为子类是一种父类类型,考虑继承关系。
如果要判断两个类型是否相同推荐使用 isinstance()。
有迭代功能的比如:列表, 元组 ,字典
如果是Iterable()的子类可以迭代的
- 2 说一下for 遍历得步骤
迭代环境会优先使用迭代协议(也就是调用__next__方法),如果对象不支持迭代协议,则使用索引方式进行迭代
(不知道大家会不会让iter返回一个列表或者字典,元组之类的可迭代得对象,这个是时候程序出现错误,查了一下可能是因为list不是一个迭代器而是一个迭代对象。其他的没在深究)
for i in xxx_obj:
pass
1 首先for会判断我们的xxx_obj是不是一个可迭代对象
2 如果满足一得话就会调用它的iter()方法,得到其中得返回值
3 然后它会接着调用iter返回值对象得next()方法当遇到StopIterable异常得时候停止遍历
- 3 如何自己实现一个可迭代的对象
用__iter__()函数来实现,代码如下(注释往往比代码更重要的奥)
from collections import Iterable
class ClassMate(object):
def __init__(self,name):
self.name = name
# 当我们加入函数__iter__()的时候这个时候他产生的对象就是可迭代对象
def __iter__(self):
print("this is iterable")
classmate = ClassMate("one")
print(isinstance(classmate, Iterable))
其中对于__iter__()这个函数返回值必须是一个具有itre和next方法得引用,上面这个虽然创建了一个迭代对象,但是不能用for进行遍历。如下代码可以遍历
,我们给类中添加一个next()方法,这个时候iter方法通过返回自己,然后就可调用next然后逐个返回,当有异常StopIteration抛出得时候for停止遍历。
from collections import Iterable
class ClassMate(object):
def __init__(self):
self.name = list()
self.count = 0
def add(self, name):
self.name.append(name)
# 当我们加入函数__iter__()的时候这个时候他产生的对象就是可迭代对象
def __iter__(self):
return self
def __next__(self):
if self.count < len(self.name):
data = self.name[self.count]
self.count += 1
return data
else:
raise StopIteration
classmate = ClassMate()
classmate.add("老王1")
classmate.add("老王2")
classmate.add("老王3")
print(isinstance(classmate, Iterable))
for i in classmate:
print(i)
- 3 迭代器的应用
先说个例子,我们都知道授人以鱼不如授人以渔,就是说我给你鱼吃不如传授给你怎么区捕鱼。为了加强理解我再说一个,就是我们饿的时候才去做饭,有没有这样的,就是我把十天的饭都做好放起来,饿了的时候不用再去做饭了,直接拿着吃就好了。但是如果我们把十年的饭都做好呢,就算做好了放在那里。而且不保鲜。就像是一大批数据,比如列表存数据,数据存放在内存中,如果有一大批数据的话可能就存不了了如果我们能找到一个方法,或者一个函数存放起来,我们通过索引取值,随时要随时生成就好了
总之:(这句有用)我们为了保护内存,我们不放数据,放的是生成数据的方法。等比数列应该都知道,我们放个公式在里面,需要的时候传个索引就出来了相应的值了。而不需要把每个值都放里面。
例如们进行斐波那契数列,如果我们需要大量的数据,我们用列表存储的话,会用很多内存,但是我存一个方法的话就想用多少,什么时候用,他就会慢慢给我取,随取随用。
from collections import Iterable
class ClassMate(object):
def __init__(self,num):
self.name = list()
self.num = num
self.count = 0
self.a = 1
self.b = 1
# 当我们加入函数__iter__()的时候这个时候他产生的对象就是可迭代对象
def __iter__(self):
return self
def __next__(self):
if self.count < self.num:
data = self.a
self.a, self.b = self.b, self.a + self.b
self.count += 1
return data
else:
raise StopIteration
classmate = ClassMate(10)
for i in classmate:
print(i)
运行结果:
生成器
- 1 生成器是一种特殊的迭代器
创建上生成器的方式- 列表推导式
nums = [x2 for x in range(10)]这个返回的是一个列表,当我们把中括号变为括号的时候,这个时候Nums = (x2 for i in range(10))这个时候它就是一个可迭代,也就是一个生成器。
- 定一个函数,让其变成生成器,函数中有yield就是一个生成器模板
加入yield之后,这个函数就是一个可迭代的对象就是一个生成器
def create_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
yield a
a, b = b, a+b
current_num += 1
"""
代码讲解:如果在调用create_ num的时候,发现这个函数中有yield那么此时,
不是调用函数,而是创建一个生成器对象
执行顺序,当执行到yield的时候,这个时候程序会在哪力停下来,然后把a的值返给for循环中的i然后输出。执行完之后,然后程序继续执行,以此循环。
因为生成器是一种特殊的迭代器,也可以通过next进行取值,如果超出取值
范围的时候就会报异常StopIteration
print(create_num(3))
obj = create_num(3)
print(next(obj))
print(next(obj))
print(next(obj))
print(next(obj))
"""
print(create_num(10))
obj = create_num(10)
for i in obj:
print(i)
通过next取值:
如果函数有个返回值怎么接受
def create_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
yield a
a, b = b, a+b
current_num += 1
"""这里有个返回值"""
return "ok"
print(create_num(3))
obj = create_num(10)
while True:
try:
ret = next(obj)
print(ret)
except Exception as ex:
print("return",ex)
break
异常中有个返回值我们处理异常的时候接受一下就可以了
与next相对应的是send,它的用法:
send使用时obj.send(发送的数据)
def create_num(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
yield a
a, b = b, a+b
current_num += 1
return "ok"
# print(create_num(3))
# obj = create_num(10)
# while True:
# try:
# ret = next(obj)
# print(ret)
# except Exception as ex:
# print("return",ex)
# break
def send_make(all_num):
a, b = 0, 1
current_num = 0
while current_num < all_num:
rep = yield a
print("这是send发送的数据", rep)
if rep:
current_num = int(rep)
a, b = b, a+b
return "ok"
obj = send_make(10)
count = 1
while True:
try:
if count == 3:
obj.send("10")
num = obj.send(None)
count += 1
print(num)
except Exception as ex:
print(ex)
break
运行结果:
提示:如果第一次使用send,需要传送一个空值因为程序从头开始执行,当我们刚开始加载程序的时候,程序没有接受send的值的参数,这个时候就会报错。所以第一次使用需要用None
协程
- 1 我们受用yield实现多任务
def task_1():
while True:
print("---1-----1-1-1-1-1-1-")
yield
def task_2():
while True:
print("---2-----22222222222")
# 在着会暂停程序,所以我可以利用它的暂停去执行其他的程序
yield
def main():
t1 = task_1()
t2 = task_2()
while True:
time.sleep(1)
next(t1)
time.sleep(1)
next(t2)
main()
运行结果
这个时候问题来了,如果有好多函数怎么办,我们还要写while循环又要添加函数太麻烦了,有没有好的方法。这个时候greenlet出来了。直接看代码:
这个包greenlet需要安装一下。
from greenlet import greenlet
def task_3():
while True:
开启这个greenlet
gr4.switch()
print("---1-----1-1-1-1-1-1-")
time.sleep(1)
def task_4():
while True:
gr3.switch()
print("---2-----22222222222")
time.sleep(1)
创建这个对象里面填入函数名
gr3 = greenlet(task_3)
gr4 = greenlet(task_4)
gr3.switch()
运行结果:
这里介绍一下gevent
greenlet已经实现了协程,但是这个还的人工切换,是不是觉得太麻烦了,不要捉急,python还有一个比greenlet更强大的并且能够自动切换任务的模块gevent
其原理是当-个greenlet遇到IO(指的是input output输入输出,比如网络、文件操作等)操作时,
比如访问网络,就自动切换到其他的greenlet,等到IO操作完成,再在适当的时候切换回来继续执行
由于I0操作非常耗时,经常使程序处于等待状态,有了gevent为我们自动切换协程,就保证总有greenlet在
运行,而不是等待I0
import time
import gevent
为了解决用普通time延时可以实现多任务的延时,我们导入此包
from gevent import monkey
然后运行这个方法就可以了
monky.patch_all()
def task_5(i):
for j in range(6):
print("1-1-1-1-",i,gevent.getcurrent())
"""gevent遇到延时才会运行其他的程序,
但是对于time,sleep这个延时不起作
用需要自己的延时gevent.sleep(10)"""
time.sleep(1)
def task_6(i):
for j in range(6):
print("2222222",i,gevent.getcurrent())
time.sleep(1)
ge1 = gevent.spawn(task_5, "this is five")
ge2 = gevent.spawn(task_6, "this is six")
ge1.join()
ge2.join()
用普通延时time.sleep()运行结果:
用gevent.sleep()延时的结果就有线程的意思了
参考代码用gevent.sleep:
放两个彩蛋
1彩蛋
迭代环境会优先使用迭代协议(也就是调用__next__方法),如果对象不支持迭代协议,则使用索引方式进行迭代
(不知道大家会不会让iter返回一个列表或者字典,元组之类的可迭代得对象,这个是时候程序出现错误,查了一下可能是因为list不是一个迭代器而是一个迭代对象,并且我在在list得源代码中找到了iter但是没找到next方法哦也许就是用的索引进行迭代的吧。其他的没在深究,有知道得大佬可以给解释一下)
如图报错了:TypeError: iter() returned non-iterator of type ‘list’
list源代码
2 彩蛋
进程、线程、协程对比
请仔细理解如下的通俗描述
●有一个老板想要开个工厂进行生产某件商品(例如剪子)
●他需要花一些财力物力制作一 条生产线,这个生产线上有很多的器件以及材料这些所有的为了能够生
产剪子而准备的资源称之为:进程
只有生产线是不能够进行生产的,所以老板的找个工人来进行生产,这个工人能够利用这些材料最终一步 步的将剪子做出来,这个来做事情的工人称之为:线程
●这个老板为了提高生产率,想到3种办法:
**1.**在这条生产线上多招些工人,一起来做剪子,这样效率是成倍增长,即单进程多线程方式
**2.**老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及材料毕竟有限,所以老
板又花了些财力物力购置了另外-条生产线,然后再招些工人这样效率又再-步提高了,即多进
程多线程方式
**3.**老板发现,现在已经有了很多条生产线,并且每条生产线上已经有很多工人了(即程序是 多进程
的,每个进程中又有多个线程), 为了再次提高效率,老板想了个损招,规定:如果某个员工在
上班时临时没事或者再等待某些条件(比如等待另一个工人生产完谋道工序之后他才能再次工
作), 那么这个员工就利用这个时间去做其它的事情,那么也就是说:如果-一个线程等待某些条
件,可以充分利用这个时间去做其它事情,其实这就是:协程方式
简单总结
1.进程是资源分配的单位
2.线程是操作系统调度的单位
3.进程切换需要的资源很最大,效率很低,
4.线程切换需要的资源一般,效率一般(当然了在不考虑GIL的情况下)
5.协程切换任务资源很小,效率高
6.多进程、多线程根据cpu核数不- -样可能是并行的,但是协程是在一个线程中所以是并发
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/119319.html