基于协程的并发
时间:2019-09-27
本文章向大家介绍基于协程的并发,主要包括基于协程的并发使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
基于协程的并发 |
一、什么是协程
协程是单线程下的并发,又称微线程,纤程。协程是一种用户态的轻量级线程,即协程是由用户程序自己控制调度的。我们需要知道的是:
#1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行) #2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(非io操作的切换与效率无关)
协程的特点是:
- 必须在只有一个单线程里实现并发;
- 修改共享数据不需加锁;
- 用户程序里自己保存多个控制流的上下文栈
- 一个协程遇到IO操作自动切换到其它协程
二、Greenlet
如果我们在单个线程内有20个任务,要想实现在多个任务之间切换,使用yield生成器的方式过于麻烦,而使用greenlet模块可以非常简单地实现这20个任务直接的切换。
#安装 pip3 install greenlet
from greenlet import greenlet def test1(): print(12) gr2.switch() #切换到gr2 print(34) gr2.switch() #切换到gr2 def test2(): print(56) gr1.switch() #切换到gr1 print(78) gr1 = greenlet(test1) gr2 = greenlet(test2) gr2.switch() #切换到gr2
单纯的切换(在没有io的情况下或者没有重复开辟内存空间的操作),反而会降低程序的执行速度。greenlet当切到一个任务执行时如果遇到io,那就原地阻塞,仍然是没有解决遇到IO自动切换来提升效率的问题。单线程里的这20个任务的代码通常会既有计算操作又有阻塞操作,我们完全可以在执行任务1时遇到阻塞,就利用阻塞的时间去执行任务2。如此,才能提高效率,这就用到了Gevent模块。
三、Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程。
具体使用如下:
#用法 g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如eat,后面可以有多个参数,可以是位置实参或关键字实参,都是传给函数eat的 g2=gevent.spawn(func2) g1.join() #等待g1结束 g2.join() #等待g2结束 #或者上述两步合作一步:gevent.joinall([g1,g2]) g1.value#拿到func1的返回值
遇到IO阻塞时会自动切换任务
import gevent import requests,time start = time.time() def f(url): print('GRT:%s'%url) resp = requests.get(url) data = resp.text print('%d bytes received from %s'%(len(data),url)) gevent.joinall([ gevent.spawn(f,'http://www.python.org/'), gevent.spawn(f,'http://www.yahoo.com/'), gevent.spawn(f,'http://www.baidu.com/'), gevent.spawn(f,'http://www.sina.com.cn/'), ]) # f('http://www.python.org/') # f('http://www.yahoo.com/') # f('http://www.baidu.com/') # f('http://www.sina.com.cn/') print('cost time:',time.time()-start)
注释部分是串行执行,在网速稳定情况下,明显使用gevent模块比串行执行效率高很多。
我们可以通过gevent实现单线程下的socket并发:
from gevent import monkey;monkey.patch_all() #from gevent import monkey;monkey.patch_all()一定要放到导入socket模块之前,否则gevent无法识别socket的阻塞 from socket import * import gevent def server(server_ip,port): s=socket(AF_INET,SOCK_STREAM) s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind((server_ip,port)) s.listen(5) while True: conn,addr=s.accept() gevent.spawn(talk,conn,addr) def talk(conn,addr): try: while True: res=conn.recv(1024) print('client %s:%s msg: %s' %(addr[0],addr[1],res)) conn.send(res.upper()) except Exception as e: print(e) finally: conn.close() if __name__ == '__main__': server('127.0.0.1',8080)
from socket import * client=socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: msg=input('>>: ').strip() if not msg:continue client.send(msg.encode('utf-8')) msg=client.recv(1024) print(msg.decode('utf-8'))
from threading import Thread from socket import * import threading def client(server_ip,port): c=socket(AF_INET,SOCK_STREAM) #套接字对象一定要加到函数内,即局部名称空间内,放在函数外则被所有线程共享,则大家公用一个套接字对象,那么客户端端口永远一样了 c.connect((server_ip,port)) count=0 while True: c.send(('%s say hello %s' %(threading.current_thread().getName(),count)).encode('utf-8')) msg=c.recv(1024) print(msg.decode('utf-8')) count+=1 if __name__ == '__main__': for i in range(500): t=Thread(target=client,args=('127.0.0.1',8080)) t.start()
原文地址:https://www.cnblogs.com/lzc69/p/11598469.html
- Redis特性和应用场景
- Spring Cloud(四)服务提供者 Eureka + 服务消费者 Feign
- 智能下拉刷新框架-SmartRefreshLayout
- Spring Cloud(三)服务提供者 Eureka + 服务消费者(rest + Ribbon)
- Spring Cloud(二)Consul 服务治理实现
- Spring Cloud(一)服务的注册与发现(Eureka)
- Shard 分片集群
- 面试官最爱的volatile关键字
- 玩转 WebView ,突破系统限制,让缓存更简单,更灵活
- Mycat 读写分离 数据库分库分表 中间件 安装部署,及简单使用
- 50道Java线程题
- Jrebel6.3.3破解,配置图文教程
- Spring Cloud(十一)高可用的分布式配置中心 Spring Cloud Bus 消息总线集成(RabbitMQ)
- Keras中带LSTM的多变量时间序列预测
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Ubuntu删除多余内核的方法
- 详解Linux下crontab的使用与注意事项
- Linux内核设备驱动之Linux内核基础笔记整理
- Ubuntu18.04 Server版安装及使用(图文)
- Ubuntu18.04安装vsftpd的实现代码
- ubuntu系统theano和keras的安装方法
- Linux安装Jenkins步骤及各种问题解决(页面访问初始化密码)
- 解决Ubuntu19 安装Theano问题
- centos7 esxi6.7模板实际应用详解
- Centos8搭建本地Web服务器的实现步骤
- 总结Linux 6种日志查看方法
- Ubuntu18.04一次性升级Python所有库的方法步骤
- linux下php安装xml扩展的详细步骤
- 查看linux文件的命令详解
- 解决Linux+Apache服务器URL区分大小写问题