协程 IO多路复用
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理论上也可以开启无限多个监听事件
浙公网安备 33010602011771号