协程 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)..................计算切换时间的时间差
yield
 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("小嘴嘴")
View Code

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理论上也可以开启无限多个监听事件

  

猜你喜欢

转载自www.cnblogs.com/ljc-0923/p/9632574.html