并发编程---协程
协程
协程: 单线程下的并发,应用程序控制的并发,是用户自己调度的,自己控制自己的速度快。
协程:单线程下的并发,并不是对性能都有所提升,而是监测单线程下的IO行为,遇到IO行为不让原地阻塞,切换到另一个任务执行。
并发: 切换任务+保存状态
单线程下实现并发:单线程下的多个任务,遇到IO就切,把单线程整体的IO降到最低。相当于把自己的IO伪装起来,让操作系统将更多的CPU分配给线程。程序用的cpu多,就叫执行效率高
总结协程特点:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需加锁
- 用户程序里自己保存多个控制流的上下文栈
- 附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
协程
greenlet模块
- 再多个任务可以很方便的切,但不能检测到,遇到IO 切
- 比yield 好,但是还是不好,遇到io不会切
from greenlet import greenlet import time def eat(name): print('%s eat1' %name) # time.sleep(10) 遇到IO不会切 g2.switch('yang') print('%s eat2' %name) g2.switch() def play(name): print('%s play1' %name) g1.switch() print('%s play2' %name) g1 = greenlet(eat) g2 = greenlet(play) g1.switch('yang') #第一次启动需要传参数 ''' 打印结果: yang eat1 yang play1 yang eat2 yang play2 '''
gevent模块
gevent: 里面封装了greenlet模块,但是可以检测到IO操作,自动切换任务
缺点:只能检测到gevent.sleep()等,gevent的IO阻塞
解决方法:导入gevent模块下的monkey方法,在文件的开头写:from gevent import monkey;monkey.patch_all()
from gevent import monkey;monkey.patch_all() import gevent import time def eat(name): print('%s eat1' %name) gevent.sleep(3) print('%s eat2' %name) def play(name): print('%s play1' %name) gevent.sleep(4) print('%s play2' %name) start_time = time.time() g1 = gevent.spawn(eat,'yang') g2 = gevent.spawn(play,'hang') g1.join() g2.join() stop_time = time.time() print(stop_time-start_time) ''' 打印结果: yang eat1 hang play1 yang eat2 hang play2 4.004916191101074 gevent换成time的话: 执行时间就变成7秒多 ''' from gevent import monkey;monkey.patch_all() import gevent import time def eat(name): print('%s eat1' %name) gevent.sleep(3) print('%s eat2' %name) def play(name): print('%s play1' %name) gevent.sleep(4) print('%s play2' %name) g1 = gevent.spawn(eat,'yang') g2 = gevent.spawn(play,'hang') # g1.join() # 等到g1执行完 # g2.join() # 等到g2执行完 gevent.joinall([g1,g2 ]) # 等到g1和g2都执行完 g1.join() g2.join() """ 打印结果: egon eat 1 alex play 1 egon eat 2 alex play 2 """
gevent模块下的套接字通信
#基于gevent实现 import socket from gevent import monkey,spawn;monkey.patch_all() def communicate(conn): while True: try: data = conn.recv(1024) if not data:break conn.send(data.upper()) except ConnectionResetError: break conn.close() def server(ip,port): server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) server.bind((ip,port)) server.listen(5) while True: conn, addr = server.accept() spawn(communicate,conn) server.close() if __name__ == '__main__': g = spawn(server,'127.0.0.1',8090) g.join()
import socket from threading import Thread,currentThread def client(): client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('127.0.0.1',8090)) while True: client.send(('%s hello' %currentThread().getName()).encode('utf-8')) data = client.recv(1024) print(data.decode('utf-8')) client.close() if __name__ == '__main__': for i in range(500): # 500个客户端同时登陆,服务端一个线程就可以接收500个client t = Thread(target=client) t.start() # 单线程的IO降下来,会大大提高程序的效率