Python之协程
协程操作是单线程进行的,协程通过自拟寄存器记录上下文和栈,实现单线程的高并发
与多线程相比,协程的特点:
1.必须在只有一个单线程里实现并发
2.修改共享数据不需加锁
3.用户程序里自己保存多个控制流的上下文栈
4.一个协程遇到I/O操作自动切换到其它协程
1.通过yield实现简单的生产者消费者模型,拟协程
# -*- coding:utf-8 -*- # Author: Wongdu import time # 消费者,通过yield把该函数变成一个生成器 def consumer(name): print("%s 准备好开始吃蛋糕啦~~~" % name) while True: # 当yield被赋值时,该函数才继续往下执行代码 dangao = yield print("[%s] 被%s吃啦~~~" % (dangao, name)) time.sleep(1) # 生产者 def producer(p_name): # 通过生成器的__next__()方法把生成器执行到yield的位置 c1.__next__() c2.__next__() n = 0 while True: dangao = '蛋糕%s' % n print("\033[41;1m[%s] 被[%s]制作出来啦~~\033[0m" % (dangao, p_name)) # 为消费者对象的yield赋值 c1.send(dangao) c2.send(dangao) # time.sleep(1) n += 1 if __name__ == '__main__': c1 = consumer(name='Caiyun') c2 = consumer(name='Dudu') p = producer(p_name='蛋糕贾师傅')
2.协程之greenlet实现手动I/O切换
# -*- coding:utf-8 -*- # Author:Wong Du ''' 协程操作是单线程进行的,协程通过自拟寄存器记录上下文和栈,实现单线程的高并发 1.必须在只有一个单线程里实现并发 2.修改共享数据不需加锁 3.用户程序里自己保存多个控制流的上下文栈 4.一个协程遇到IO操作自动切换到其它协程 ''' ''' 通过greenlet实现协程间的手动切换 ''' from greenlet import greenlet def run1(): print(12) # 切换到gr2协程执行 gr2.switch() print(34) gr2.switch() def run2(): print(56) # 切换到gr1协程执行 gr1.switch() print(78) # 注册协程,记录当前协程状态(类似线程中寄存器位置状态记录) gr1 = greenlet(run1) gr2 = greenlet(run2) # 切换到gr1协程执行 gr1.switch()
3.协程之gevent实现自动I/O切换
# -*- coding:utf-8 -*- # Author:Wong Du ''' 1.在协程中,通常在遇到I/O读写阻塞时,才进行协程间切换 2.默认情况下,gevent只能识别gevent下的I/O阻塞,如gevent.sleep(),不能识别time.sleep()阻塞 3.通过monkey.patch_all()方法,可以识别python中大部分的I/O阻塞,如time.sleep() 4.下面代码介绍gevent实现自动I/O切换 ''' import gevent import time # 通过monkey里的patch_all方法,可以识别python中基本全部I/O阻塞 # from gevent import monkey # monkey.patch_all() # 记录程序开始执行时间 start_time = time.time() def func1(): print("Running in the func1...") # 模拟I/O阻塞,睡2秒 gevent.sleep(2) # time.sleep(2) print("\033[31;1mRunning in the func1 again...\033[0m") def func2(): print("Running in the func2...") # 模拟I/O阻塞,睡1秒 gevent.sleep(1) # time.sleep(1) print("\033[32;1mRunning in the func2 again...\033[0m") def func3(): print("Running in the func3...") # 模拟I/O阻塞,睡0.5秒 gevent.sleep(0.5) # time.sleep(0.5) print("\033[33;1mRunning in the func3 again...\033[0m") # 通过joinall方法以列表的方式注册多个协程 gevent.joinall( [ # 注册和自动运行协程,并记录当前协程状态(类似线程中寄存器位置状态记录) gevent.spawn(func1), gevent.spawn(func2), gevent.spawn(func3), ] ) # 计算程序执行完成花费时间 print("\033[41;1mspeed time:%s\033[0m" % (time.time()-start_time)) ''' 运行结果: Running in the func1... Running in the func2... Running in the func3... Running in the func3 again... Running in the func2 again... Running in the func1 again... speed time:2.014172315597534 '''
4.两个协程实现简单异步效果实例
4.1 爬虫之协程gevent
1 # -*- coding:utf-8 -*- 2 # Author:Wong Du 3 4 import time, gevent 5 from urllib.request import urlopen 6 7 # 为当前程序的所有io操作添加gevent可识别的标记,效果类似gevent.sleep() 8 from gevent import monkey 9 monkey.patch_all() 10 11 # 爬虫函数 12 def func(url): 13 print("Get:", url) 14 # 打开网页url并接收返回结果 15 resq = urlopen(url) 16 # 读取请求返回数据的html页面,以字符串的格式 17 data = resq.read() 18 print("%s size: %s" % (url, len(data))) 19 20 # 要爬取的网页url列表 21 urls = [ 22 'https://www.python.org/', 23 'https://www.imooc.com/', 24 'https://www.cnblogs.com/', 25 ] 26 27 # 计算通过同步执行爬虫程序花费的时间 28 sync_start_time = time.time() 29 for url in urls: 30 func(url) 31 print("\033[31;1msync speed time: %s\033[0m" %(time.time()-sync_start_time)) 32 # 结果:sync speed time: 3.6082065105438232 33 34 # 计算通过协程异步执行爬虫程序花费的时间 35 async_start_time = time.time() 36 gevent.joinall( 37 [ 38 gevent.spawn(func, 'https://www.python.org/'), 39 gevent.spawn(func, 'https://www.imooc.com/'), 40 gevent.spawn(func, 'https://www.cnblogs.com/'), 41 ] 42 ) 43 print("\033[32;1masync speed time: %s\033[0m" % (time.time()-async_start_time)) 44 # 结果:async speed time: 0.5860333442687988
4.2 通过gevent实现一个简单的异步socket连接处理,实现单线程下的socket连接高并发
1 # -*- coding:utf-8 -*- 2 # Author:Wong Du 3 4 import socket 5 HOST = "localhost" 6 Port = 8001 7 client = socket.socket() 8 client.connect((HOST, Port)) 9 10 while True: 11 cmd = input("%s#" % HOST) 12 if not cmd: 13 continue 14 client.send(cmd.encode()) 15 data = client.recv(1024) 16 print(data.decode())
1 # -*- coding:utf-8 -*- 2 # Author:Wong Du 3 4 import socket, gevent 5 6 # 为当前程序的所有io操作添加gevent可识别的标记,效果类似gevent.sleep() 7 from gevent import monkey 8 monkey.patch_all() 9 10 # 建立连接,并注册协程 11 def server(port): 12 serve = socket.socket() 13 serve.bind(("0.0.0.0", port)) 14 serve.listen(3) 15 16 while True: 17 # 等待连接接入,阻塞,当有连接接入时,往下执行 18 conn, addr = serve.accept() 19 20 # 注册协程,通过gevent记录并监测该协程的状态(阻塞时切换) 21 gevent.spawn(handle_request, conn) 22 23 # 连接处理函数 24 def handle_request(conn): 25 print(conn) 26 try: 27 while True: 28 res = conn.recv(1024) 29 print(res.decode()) 30 conn.send(res) 31 if not res: 32 print("client %s is not connected..." % conn.addr) 33 conn.shutdown(socket.SHUT_WR) 34 except Exception as e: 35 print(e) 36 finally: 37 print("---exec done---") 38 conn.close() 39 40 if __name__ == "__main__": 41 server(8001)
静静的学习一阵子儿...