并发编程 - 协程 - 总结
协程:
单线程下实现并发 并发 = 切换 + 保存状态
1.遇到IO切, 提高效率
2.遇到计算切, 并没有提高效率
1.协程本质:
协程的本质就是在单线程下,由用户自己控制一个任务遇到io阻塞了就切换另外一个任务去执行,以此来提升效率。
为了实现它,我们需要找寻一种可以同时满足以下条件的解决方案:
1. 可以控制多个任务之间的切换,切换之前将任务的状态保存下来,以便重新运行时,可以基于暂停的位置继续执行。
2. 作为1的补充:可以检测io操作,在遇到io操作的情况下才发生切换
2.强调:
1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)
2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)
3.优点:
1. 协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
2. 单线程内就可以实现并发的效果,最大限度地利用cpu
4.缺点:
1. 协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
2. 协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
5.总结:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈
4.附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
单线程下实现并发:
1.yield # yield 遇到io 不能监测到 切换 # 没有io 去切换,会降低执行速度
2.greenlet 模块 # greenlet 遇到io 不能监测到 切换 # 没有io 去切换,会降低执行速度
pip3 install greenlet
greenlet 比yield 好 但是还是不好 遇到io不会切
g1 = greenlet(eat)
g1.switch('alice')
g1.switch()
3.gevent 模块
pip3 install gevent
gevent:封装了greenlet模块,但是他能检测到io 自动切
加了补丁:from gevent import monkey;monkey.patch_all()
遇到time.sleep(2) 才会切,否则只有遇到gevent.sleep(2) 才会切,所以必须加补丁
g1 = gevent.spawn(eat,'alice')
g1.join() # 等待g1结束
gevent.joinall([g1,g2])
g1.value # 拿到func1的返回值
补丁说明:
from gevent import monkey;monkey.patch_all()
1.必须放到被打补丁者的前面,如time,socket模块之前
2.要用gevent,需要将from gevent import monkey;monkey.patch_all()放到文件的开头
# 通过gevent实现单线程下的socket并发(from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,
否则gevent无法识别socket的阻塞)
1 import time 2 def consumer(res): 3 '''任务1:接收数据,处理数据''' 4 pass 5 6 def producer(): 7 '''任务2:生产数据''' 8 res=[] 9 for i in range(10000000): 10 res.append(i) 11 return res 12 13 start=time.time() 14 #串行执行 15 res=producer() 16 consumer(res) #写成consumer(producer())会降低执行效率 17 # consumer(producer()) 18 stop=time.time() 19 print(stop-start) #1.5536692142486572
1 import time 2 def producter(): # 并发的去执行 3 g = consumer() 4 next(g) 5 for i in range(10): 6 g.send(i) 7 time.sleep(2) 8 9 def consumer(): 10 while True: 11 res = yield 12 print(res) 13 start = time.time() 14 producter() 15 stop = time.time() 16 print(stop-start) 17 18 import time 19 def producter(): # 串行 计算型的 切换 会降低运行效率 20 res = [] 21 for i in range(10000000): 22 res.append(i) 23 return res 24 25 def consumer(res): 26 pass 27 28 start = time.time() 29 res = producter() # 串行 执行 写成 consumer(producter()) 会降低执行效率 30 consumer(res) 31 stop = time.time() 32 print(stop-start)
1 from greenlet import greenlet 2 import time 3 4 def eat(name): 5 print('%s eat 1'%name) 6 7 g2.switch('alice') 8 time.sleep(4) # 遇到io 不会立即s切 9 print('%s eat 2'%name) 10 g2.switch() 11 12 def play(name): 13 print('%s play 1'%name) 14 g1.switch() 15 print('%s play 2'%name) 16 17 g1 = greenlet(eat) 18 g2 = greenlet(play) 19 20 g1.switch('alice') # 第一次切 需要传参数
1 #顺序执行 2 import time 3 def f1(): 4 res=1 5 for i in range(100000000): 6 res+=i 7 8 def f2(): 9 res=1 10 for i in range(100000000): 11 res*=i 12 13 start=time.time() 14 f1() 15 f2() 16 stop=time.time() 17 print('run time is %s' %(stop-start)) #10.985628366470337 18 19 #切换 20 from greenlet import greenlet 21 import time 22 def f1(): 23 res=1 24 for i in range(100000000): 25 res+=i 26 g2.switch() 27 28 def f2(): 29 res=1 30 for i in range(100000000): 31 res*=i 32 g1.switch() 33 34 start=time.time() 35 g1=greenlet(f1) 36 g2=greenlet(f2) 37 g1.switch() 38 stop=time.time() 39 print('run time is %s' %(stop-start)) # 52.763017892837524
1 from gevent import monkey;monkey.patch_all() 2 import gevent 3 import time 4 5 def eat(name): 6 print('%s eat 1'%name) 7 time.sleep(2) 8 # gevent.sleep(2) 9 print('%s eat 2'%name) 10 11 def play(name): 12 print('%s play 1'%name) 13 # gevent.sleep(1) 14 time.sleep(1) 15 print('%s play 2'%name) 16 17 start = time.time() 18 g1 = gevent.spawn(eat,'alice') 19 g2 = gevent.spawn(play,'alice') 20 # g1.join() 21 # g2.join() 22 gevent.joinall([g1,g2]) 23 stop = time.time() 24 print('主',stop-start) 25 """ 26 alice eat 1 # 遇到 time.sleep() io 并不会切 加个补丁才会切 27 alice eat 2 28 alice play 1 29 alice play 2 30 主 3.000598430633545 31 """ 32 """ 33 alice eat 1 # 加了补丁后,可识别time.sleep() io 会切 34 alice play 1 # 补丁:from gevent import monkey;monkey.patch_all() 35 alice play 2 36 alice eat 2 37 主 2.000128984451294 38 """ 39 """ 40 alice eat 1 # 遇到 gevent.sleep() 会切 41 alice play 1 42 alice play 2 43 alice eat 2 44 主 2.0026285648345947 45 """
1 # -*- coding:utf-8 -*- 2 from gevent import spawn,monkey;monkey.patch_all() 3 import socket 4 5 #如果不想用money.patch_all()打补丁,可以用gevent自带的socket 6 # from gevent import socket 7 # s=socket.socket() 8 9 def talk(conn): 10 while True: 11 try: 12 res = conn.recv(1024) 13 if not res:break 14 conn.send(res.upper()) 15 except Exception as e: 16 break 17 conn.close() 18 19 def server(ip,port): 20 server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 21 server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 22 server.bind((ip,port)) 23 server.listen(5) 24 while True: 25 conn, addr = server.accept() 26 spawn(talk,conn) 27 28 if __name__ == '__main__': 29 g = spawn(server,'127.0.0.1',8080) 30 g.join()
1 # -*- coding:utf-8 -*- 2 import socket 3 from threading import Thread,currentThread 4 5 def client(ip,port): 6 c = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 7 c.connect((ip,port)) 8 9 while True: 10 c.send(('%s hello '%currentThread().getName()).encode('utf-8')) 11 data = c.recv(1024) 12 print(data.decode('utf-8')) 13 14 if __name__ == "__main__": 15 for i in range(500): 16 t = Thread(target=client,args=('127.0.0.1',8080)) 17 t.start()