1,协程:背景:因为想要在单线程内实现并发的效果(核心的目标是更加充分的使用CPU的性能)
协程是在线程的基础上衍生出来的一个概念,
所以:协程是一个比线程更加轻量级的单位,是组成线程的各个函数
因为GIL锁的存在,限制了再同一时间点,只能执行一个线程,所以想要在执行一个线程的期间,充分利用CPU的性能,所以才有了单线程内实现并发的效果
并发:切换 + 保存状态
1.2>CPU切换的原因:
1.2.1>因为某个程序阻塞了
1.2.2>因为某个程序用完了时间片
很显然,在解决第一个问题的时候会提高CPU的使用效率,所以想要实现单线程的并发,就要解决解决在单线程内,多个任务函数中,某个任务函数遇见IO操作,马上自动切换到其他任务函数去执行.,协程的本身没有实体
1 import time 2 def consumer(): 3 while 1 : 4 x = yield 5 print(x) 6 def producer(): 7 g = consumer().....................在producer函数里调用consumer函数 8 next(g)................................只要程序执行遇到next的时候,就会执行一个yield 9 for i in range(10000): 10 g.send(i).......................给yield的变量发送一个值 11 12 start = time.time() 13 14 producer()...............................调用producer函数 15 16 print("yield",time.time - start)..................计算切换时间的时间差
1 import time 2 def func(): 3 sum = 0..................定义一个sum为0 4 while 1: 5 sum += 1 6 print(sum).....................以运行程序,就先打印这个sum 7 yield sum....................当for循环第一个next的是后,成序走到这一行下一个next的时候原形到yield之前. 8 print(sum) 9 g = func() 10 for i in range(10): 11 next(g) 12 13 fori in range(10): 14 15 next(g).....................执行一个next就执行一个yield,并且走完yield为止 16 17 time.sleep(3)
打印的结果是先打印1.程序休眠3秒,再打印1,2等待3秒,在打印2,3依次类推.
在单线程中,如果存在多个函数,如果某个函数发生IO操作,我想让程序马上去切换另一个函数去执行,以此来实现一个并发的假象
总而言之,言而总之:
yield只能实现单纯的切换函数和保存函数状态的功能,不能实现某一个函数遇到 IO阻塞时,自动的切换到另一个函数去执行,目标是:当某一个函数中遇到IO阻塞的时候,程序能自动的切换到另一个函数中去执行,如果能实现这个功能,那么,每个函数都是一个协程
协程的本质还是靠yield去实现的,如果只是拿yield去单纯的实现一个切换的现象,此时没有程序串行执行效率高
1 from concurrent.futures import ThreadPoolExecutor
2
3 feom threading import current_thread
4
5 import time
6
7 def func(i):
8 sum = 0
9
10 sum = sum + i
time.sleep(1)..........................必须加时间休眠,避免回调函数会提前打印出线程号,和主线程的线程号一样
11 print(sum,current_thread()).........打印经过处理的参数和线程号
12
13 return sum..................................返回一个sum作为回调函数的参数
14 de cal_back(num):
15 sum = num.result() - 1.....................此时接收的的参数必须要result()去获取
16 print(sum,current_thread())
17 if __name__ == "__main":
18
19 t = ThreadPoolExecutor(2)
20
21 for i in range(5):
22
23 t.submit(func,i).add_done_callback(cal_back)...............提交5个任务
24
25 t.shutdown()..................关闭线程池
2,greenlet模块:能简单的实现函数与寒素之间的切换,但是遇到IO操作,不能自动切换到其他函数中
2.1>注册一下函数func,将函数注册成一个f1的对象
f1 = greenlet(func)
2.2>调用func,使用f1.switch(),如果func需要传参数,就在switch这里传即可
1 from greenlet import greenlet 2 import time 3 def eat(name): 4 print("%s在吃炸鸡"%name) 5 f1.switch("小雪")...................有停止的意思和转化到drink的函数去执行 6 7 print("%s在吃老冰棍"%name) 8 9 f1.switch() 10 11 print("%S在吃泡馍"%name) 12 13 def drink(name): 14 15 print("%s在和啤酒"%name) 16 17 f2.switch() 18 19 print("%s在和白开水"%name) 20 21 f2.switch() 22 23 f1 = greenlet(eat) 24 25 f2 = greenlst(drink) 26 27 f1.switch("小嘴嘴")
greenlet 只是可以实现一个简单的切换的功能,还是不能做到遇到IO就切换
g1 = greenlet(func).........去实例化一个对象
g1.switch()用这种方式去调用func函数
switch调用函数的时候,当遇到return和switch会停止运行
3,gevent模块:可以实现在某函数内部遇到IO操作,就自动的切换到其他函数中去执行
g = gevent.spawn(func,参数)注册一下函数func,返回一个对象g
gevent.join(g).......等待g指向的函数func执行完毕,如果在执行过程中,遇到IO,就切换
gevent.joinall([g1,g2,g3]).........等待g1 g2 g3指向的函数func执行完毕
3.1>只识别自己的阻塞,譬如gevent.sleep()就不会阻塞,直接过获取
1 import gevent 2 import time 3 def func1(): 4 5 print("xiaoxue") 6 7 gecent.sleep(1).................到遇到自己的阻塞等待的是后就直接切换另一个函数 8 9 print("xuexue") 10 11 genevt.sleep(1) 12 13 print("miss deeply") 14 15 def func2(): 16 17 print("xueer") 18 19 time.sleep(1)..............不能识别其他的IO操作,只能识别自己的IO操作 20 21 g1 = gevent.spawn(func1).............实例化一个 22 23 g2 = gevent.spawn(func2) 24 25 g1.join().................等待g1的指向任务结束 26 27 g2.join()...................等待g2的指向任务结束
3.2>解决只能识别自己阻塞的问题..........monkey模块
1 from gevent import monkey 2 monkey.patch_all()................能识别大部分IO操作 3 import gevent 4 import time 5 def func1(): 6 print("xuexue") 7 gevent.sleep(1).....................遇到等待就直接执行下一个函数 8 print("xiaoxue") 9 time.sleep(1) 10 print("miss deeply") 11 input(">>>")...............................此时遇到新濮阳还是会阻塞主 12 def func2(): 13 print("nil") 14 gevent.sleep(1) 15 g1 = gecent.spawn(func1)..................分裂实例化个对象里边传函数 16 g2 = gevent.spawn(func2) 17 g1.join()...........................等待g1的任务执行结束 18 g2.join()
4,IO多路复用
阻塞IO, 非阻塞IO,多路复用IO,异步IOpython实现不了,但是有tornado框架,天生自带异步
4.1>非阻塞IO模型解决阻塞IO
1 import socket 2 sk = socket.socket() 3 sk.bind(("127.0.0.1",8001)) 4 sk.listen() 5 sk.blockset(False) 6 lst = [] 7 del_lst = [] 8 while 1: 9 try: 10 conn,addr = sk.accept().......处理accept的阻塞,只有有客户端连接了走这里 11 lst.append(conn)...............把已经连接的客户端添加大一个列表中 12 13 for conn in lst:...............遍历这个列表,看一下客户端是不是发了消息 14 15 try: 16 17 info = conn.recv(1024).decode("utf-8").....conn试着去接收哦消息 18 if info == None:..................判断conn是不是接收到了消息 19 20 print("客户端退出了...")..如果conn没有收到消息,说明客户端连接上 了就退出了 21 22 conn.close()......客户端主动关闭,服务器也要主动关闭 23 24 del_lst.append(conn)....把这些空的conn添加到要删除的列表中 25 26 else: 27 28 print(info.upper().encode("utf-8"))......否则,conn接收到了消息,就反馈给客户端 29 30 except BlockingIOError:第一种阻塞时没有客户端连接 31 32 continue 33 34 except ConnectionResetError:....第二种是因为客户端强制退出 35 36 continue 37 38 if len(del_lst) > 0: 39 40 for i in del_lst: 41 42 lst.remove(i) 43 44 del_lst.clear()..........要把要删除的列表清空,也要补然hi赢向下一次循环 45 except BlockingIOError:..................没有客户端就会走 这 46 47 continue
4.2>基于select的网络IO模型
1 import select 2 import socket 3 4 sk = socket.socket() 5 6 sk.bind(("127.0.0.1",8008)) 7 8 sk.listen() 9 10 rlist = [sk]...........用select来帮忙监听所有的接口 11 while 1: 12 13 r,w,x = select.select(rlist,[],[])...传参数给selec,当rlist列表中那个接口有反应就返回给r这个列表 14 if len(r) >0: 15 for i in r:...............循环遍历,查看反应的接口是sk,还是conn 16 if i == sk:......如果是sk那表示有客户请求连接 17 conn,addr = i.accept()..........把已经连接成功的conn 18 rlist.append(conn)..........把连接成功的conn添加到rlist中 19 20 21 else:........如果是conn就表示客户端要发数据了 22 23 msg = i.recv(1024).decode("utf-8") 24 25 try: 26 27 if i == None: 28 29 rlist.remove(i).............删除那些连接成功却没有发消息的conn 30 31 i.close().......客户端执行了close,服务器也执行close 32 33 else: 34 35 print(msg) 36 37 i.send(msg.upper().encode("ytf-8")) 38 39 except ConnectionReswtError: 40 41 continue
#########################################select和poll和epoll的区别###########################
select和poll有一个共同的机制,都是采用轮训的方式去询问内核,去查看哟没有 数据准备好了
select是一个最大监听事件的限制,32位机限制1024,6位机限制2048
poll没有,理论上poll可以开启无限大,1G内存大概够开10w个事件去监听
epoll是最好的,采用的是回调机制,解决了select和poll共同存在的问题
而且epoll理论上也可以开启无限多个监听事件