学习知识点:
1.知识点叫什么
2.知识点用在哪
3.知识如何实现
一. 线程、进程、协程傻傻分不清楚
1.进程 :启动多个进程 进程之间是由操作系统负责调用
线程 :启动多个线程 真正被cpu执行的最小单位实际是线程
协程:
本质是是一个线程
能够在多个任务之间切换来节省一些IO时间
携程中任务之间的切换时间也消耗时间,但是时间开销要远远小于进程线程之间的切换
二. 协程最关键的点就是:gevent
1.gevent是什么?
网络异步并发库,底层yield实现,对greenlet再次封闭
2.作用:实现多任务,是效率最快,占用资源最少的一种方法
3. 协程的 重点四步:
4.使用gevent这个模块,必须要请猴子来打补丁,下面代码感受一下。
1 import gevent 2 import time 3 4 5 def song(): 6 for i in range(5): 7 print("唱歌") 8 time.sleep(1) 9 10 11 def dance(): 12 for i in range(5): 13 print("跳舞") 14 time.sleep(1) 15 16 17 def main(): 18 """一边唱歌一边跳舞""" 19 # 加入协程 20 song_g = gevent.spawn(song) 21 dance_g = gevent.spawn(dance) 22 23 # 让我们的主线程等待协程的任务做完 24 song_g.join() 25 dance_g.join() 26 27 28 if __name__ == '__main__': 29 main()
打印结果:time休眠并没起到作用 唱歌 唱歌 唱歌 唱歌 唱歌 跳舞 跳舞 跳舞 跳舞 跳舞
1 import gevent 2 import time 3 from gevent import monkey # 请猴子打补丁 4 5 # 用到协程就请猴子 打补丁 6 monkey.patch_all() 7 8 9 def song(): 10 for i in range(5): 11 print("唱歌") 12 time.sleep(1) 13 14 15 def dance(): 16 for i in range(5): 17 print("跳舞") 18 time.sleep(1) 19 20 21 def main(): 22 """一边唱歌一边跳舞""" 23 # 加入协程 24 song_g = gevent.spawn(song) 25 dance_g = gevent.spawn(dance) 26 27 # 让我们的主线程等待协程的任务做完 28 song_g.join() 29 dance_g.join() 30 31 32 if __name__ == '__main__': 33 main()
打印结果: 唱歌 跳舞 唱歌 跳舞 唱歌 跳舞 唱歌 跳舞 唱歌 跳舞
注意点:
1.其实也可以不用请猴子打补丁,只需要把time.sleep() 改成 gevent.sleep() 就可以了,不过这样很麻烦,一般请个猴子打补丁之后,代码该咋写就咋写。下面两张图,感受一下:
2.gevent碰到延时的自动切换,所有的耗时的都需要gevent库中的耗时方法进行更换太麻烦。
5. 其实上面的代码也是有缺陷的,join() 只能等待一个协程,如果很多怎么办?这时候就需要joinall().
joinall与join的区别:
joinall可以用在单任务等待,也可以用在批量任务等待
join只能用在单任务等待
joinall是把列表中的所有等待的时间都执行了在结束,
join只能等待一个
1 # 1. 导入gevent 2 # 2. 请求猴子打补丁 3 # 3. 加入任务 4 # 4. 加入等待列表中 5 6 7 import gevent 8 import time 9 10 from gevent import monkey 11 12 # 打补丁 13 monkey.patch_all() 14 15 16 # 定义任务 17 def song(): 18 while True: 19 print("唱歌") 20 time.sleep(1) # 打了补丁以后,在执行代码时会把当前的耗时代码换成gevent的耗时代码 21 # gevent.sleep(1) # 必须使用gevent中的耗时操作 22 23 24 def dance(): 25 for temp in range(5): 26 print("跳舞") 27 time.sleep(1) 28 29 30 def main(): 31 """使用协程去执行多任务""" 32 # 加入到任务中 33 song_g = gevent.spawn(song) 34 dance_g = gevent.spawn(dance) 35 36 # 加入到等待列表中 37 gevent_list = list() # 空的列表 38 # dict() 空的字典 39 # 加入到列表中 40 gevent_list.append(song_g) 41 gevent_list.append(dance_g) 42 43 # 让我们的主线程等待 44 gevent.joinall(gevent_list) # 等待的名单 45 46 47 # 结束 48 49 50 if __name__ == '__main__': 51 main()
打印结果: 唱歌 跳舞 唱歌 跳舞 唱歌 跳舞 唱歌 跳舞 唱歌 跳舞 唱歌 唱歌 唱歌 唱歌 唱歌 唱歌
重点:以后协程使用就按照上面四步来就可以了。来张图增加记忆。
6.协程的传参
song_g = gevent.spawn(song, "python19", 70) # 第一个函数的引用,第二个以后都是参数值
二. 迭代器
1.先对迭代器来个概念:
Python中iterable被认为是一个对象,这个对象可以一次返回它的一个成员(也就是对象里面的元素),由此可知,Python中的string,list,tuple,dict,file,xrange都是可迭代的,都属于iterable对象,
可迭代的对象都是可以遍历的,实际上Python中有很多iterable类型是使用iter()函数来生成的。
2.什么是迭代器: 访问集合元素的一种方式
3.迭代器的作用: 可以通过for循环来获取实现迭代的类中存的数据。在数据有规则的情况下,迭代器毫无疑问是内存使用最小的,所以迭代器一般用于科学计算。
注意点:没有规则(无序的,无公式的)无法使用迭代器。
4.如何判断是不是迭代器。
5. 自定义迭代器,需要两个魔法方法,来个图
1 # 迭代器就是一个可以被for遍历的对象 2 # 实现两个方法__iter__和__next__ 3 # 如果只实现__iter__这个不是迭代器只是一个可迭代的对象 4 5 6 class MyIter(object): 7 8 def __init__(self): 9 self.num = 0 # 初始化的值 10 11 # 这个iter必须返回迭代器对象 12 def __iter__(self): 13 return self # 在自定义迭代器时,一般都是返回self 14 15 def __next__(self): 16 # 停止迭代 17 self.num += 1 18 # 如果大于50停止 19 if self.num > 5: 20 # 这个有点意思,当停止迭代时,是报错的,所以需要手动抛出异常 21 raise StopIteration 22 return self.num 23 24 for temp in MyIter(): 25 print(temp) # 这个打印出来是来是1-5 这也很有意思 26 27 # 做个试验 28 a = list() # 系统自带的列表,打印出来就是0-4,至今搞不清楚 29 for temp in range(5): 30 a.append(temp) 31 for temp in a: 32 print(temp) # 也是5个数据,不过是0-4
打印结果: 1 2 3 4 5 0 1 2 3 4
6.自定义迭代器与已有集合区别
1.迭代器存的是生成的公式,列表存的是具体的值
2.迭代器占用空间会小很多,来个案例看下内存的占用
3.迭代器必须是有规律的可以用公式才能体现省内存
4.迭代器使用内存比我们的列表要小,前提是数据必须有规则,如果没有规则 ,那么一般不能使用迭代器
前置知识查看内存:http://blog.csdn.net/xiaodongxiexie/article/details/54633049
1 # 迭代器就是一个可以被for遍历的对象 2 # 实现两个方法__iter__和__next___ 3 # 如果只实现了 __iter___这个不是迭代器只是一个可迭代的对象 4 5 # 迭代器使用内存比我们的列表要小,前提是数据必须有规则,如果没有规则 ,那么一般不能使用迭代器 6 # 迭代器用在科学计算 7 8 class MyIter(object): 9 def __init__(self): 10 self.num = 0 # 初始化的值 11 12 # 这个iter必须返回迭代器对象 13 def __iter__(self): 14 # print("iter") 15 return self # 在自定义迭代器时,一般都是返回self 16 17 # 返回迭代时的值 18 def __next__(self): 19 # print("next") 20 # 停止迭代 21 self.num += 2 22 # 判断如果大于50停止 23 if self.num > 1000000: 24 # 停止 25 # 手动抛出异常 26 raise StopIteration # 停止迭代 27 28 return self.num 29 30 31 # 得到迭代器对象 32 my_iter = MyIter() 33 34 for temp in my_iter: 35 print(temp) 36 37 # 常用的: 38 import psutil 39 import os 40 41 # 查看内存 42 info = psutil.virtual_memory() 43 print(u'内存使用:', psutil.Process(os.getpid()).memory_info().rss) 44 print(u'总内存:', info.total) 45 print(u'内存占比:', info.percent) 46 print(u'cpu个数:', psutil.cpu_count())
7.xrange与range的区别
xrange在python2中存在,不要存实际的数据,存的是公式省内存
python3与python2的range
8.iter与next方法使用,理解:迭代器就好比你父亲原先是一次性给你一百万,现在是你需要多少给你多少,给完就结束
9.for的本质,我快看晕了,先放这吧。
1 # 来探究一下for的本质 2 3 class MyIter(object): 4 5 def __init__(self): 6 self.num = 0 7 8 def __iter__(self): 9 return self 10 11 def __next__(self): 12 self.num += 2 13 if self.num > 10: 14 raise StopIteration 15 return self.num 16 17 # 下面的代码才是本质 18 # iter方法得到迭代器 19 my_iter = MyIter() 20 # 得到迭代器 这个iter是个内建函数 21 iter_my = iter(my_iter) # 调用__iter__方法 22 # 迭代一次 23 print(next(iter(my_iter))) # 调用__next__方法 24 print(next(iter(my_iter))) # 调用__next__方法 25 print(next(iter(my_iter))) # 调用__next__方法 26 print(next(iter(my_iter))) # 调用__next__方法 27 print(next(iter(my_iter))) # 调用__next__方法
打印结果: 2 4 6 8 10
三. 生成器
1.什么是生成器: 一种特殊的迭代器
2.作用: 跟迭代器作用一致
3.如何写一个生成器?2种方法
1.列表推导式生成元组的方式就是生成器对象, 把[] 改成 () 就是生成器了
2.使用yield关键字
1 # 只要你的函数有了yield关键字,那么就是生成器了,就是for 2 def my_iter(): 3 for temp in range(10): 4 yield temp # 这个是暂停 5 6 7 print(my_iter()) 8 9 for temp in my_iter(): 10 print(temp)
打印结果: <generator object my_iter at 0x0000017B4C07B518> 0 1 2 3 4 5
4.给生成器执行过程中传值 send的使用
1 # send 跟next作用是一样,但是send可以传参 2 3 def My_iter(): 4 for temp in range(10): 5 print(temp) 6 # 接收值 7 value = yield temp 8 print(value) 9 10 print("打印") 11 12 # 得到迭代器对象 13 iter_my = iter(My_iter()) 14 # 迭代 15 next(iter_my) # 这个只打印temp 碰到yield就停止不打印了 16 # send 只能在这之后使用,不然报错 17 next(iter_my) # 这个从yield处接着向下执行,知道再次碰到yield 18 next(iter_my) 19 iter_my.send('haha') # 在暂停启动后可以传参数,了解一下,只能第二次使用
打印结果: 0 None 打印 1 None 打印 2 haha 打印 3
5.小结
1.迭代第一次全部执行到yeild结束,返回结果,第二次以后的执行都是在yeild后面的代码执行
2.普通的方法只要写了yield就是生成器
3.yield有点像我们的input()函数,只有用户输入值才会返回,yield需要用户调用next()或者 for才执行
4.通过next()这个函数进行返回值,每次只返回一个值,是从0下标开始,直到元素取完,结束(报错)
5.generator保存的是算法,每次调用next(),就计算出下一个元素的值,直到最后一个元素,没有更多元素时,抛出StopIteration的错误
6.使用yield完成多任务
1. 作用:可以利用耗时操作的空闲时间提高效率
2.与进程,线程对比:协程切换任务资源最少,就像调用函数一样简单
四. greenlet 了解
1.是什么:对yield进封装
2.作用:实现多任务
3.代码实现
1 import greenlet 2 import time 3 # 使用greenlet 需要手动把所有函数连接起来,形成一个闭环的效果 4 5 6 def song(): 7 while True: 8 print("唱歌") 9 time.sleep(1) 10 # 切换到跳舞 11 dance_g.switch() 12 13 14 def dance(): 15 while True: 16 print("跳舞") 17 time.sleep(1) 18 # 切换到唱歌 19 song_g.switch() 20 21 # 加入两个任务 22 song_g = greenlet.greenlet(song) 23 dance_g = greenlet.greenlet(dance) 24 25 # 开启 都可以开启,只不过是谁先的问题 26 song_g.switch() 27 dance_g.switch()
打印结果: 唱歌 跳舞 唱歌 跳舞
4.来个案例,从网上下载图片
1 # 创建一个批量的图片的列表 2 # 循环的去下载 图片 3 4 # 协程根据耗时进行自动切换 5 6 7 from urllib.request import * 8 9 import gevent 10 11 from gevent import monkey 12 13 # 打补丁 14 monkey.patch_all() 15 16 17 def down_image(down_path, save_path): 18 """ 19 下载图片 20 :param down_path: 下载的地址 21 :param save_path: 保存的地址 22 :return: NONE 23 """ 24 25 print("下载图片开始:", save_path) 26 27 # 打开下载的图片,得到内容 28 image = urlopen(down_path) 29 # 得到内容 30 content = image.read() 31 32 # 保存 33 with open(save_path, 'wb') as f: 34 f.write(content) 35 36 print("下载图片结束:", save_path) 37 38 39 def main(): 40 """批量下载图片""" 41 # 创建一个批量图片的列表 42 image_list = list() 43 image_list.append("https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=3506739232,2945471821&fm=27&gp=0.jpg") 44 image_list.append("https://ss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1293860636,1088191402&fm=27&gp=0.jpg") 45 image_list.append("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=3711458043,3147749033&fm=27&gp=0.jpg") 46 47 # 图片的位置 48 num = 1 49 50 # 创建一个协程等待的列表 51 gevent_list = list() 52 53 # 循环去下载 54 for temp in image_list: 55 # 下载 56 # down_image(下载的路径,保存的路径) 57 save_path = "./images/%s.jpg" % num 58 # 把耗时的任务放到多任务中 59 # 把任务 加入到协程中 60 g_down_image = gevent.spawn(down_image, temp, save_path) 61 62 # 加入到等待的列表中 63 gevent_list.append(g_down_image) 64 65 # down_image(temp, save_path) 66 # 图片的顺序加1 67 num += 1 68 69 # 让主线程等待 70 gevent.joinall(gevent_list) 71 72 73 if __name__ == '__main__': 74 main()
五. 总结
1. 进程、线程、协程之间的关系
2.进程、线程、线程的对比