python2.0_s12_day9_协程&Gevent协程
Python之路,Day9 - 异步IO\数据库\队列\缓存
本节内容 Gevent协程
Select\Poll\Epoll异步IO与事件驱动
Python连接Mysql数据库操作 协程
1.协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是协程:协程是一种用户态的轻量级线程。(操作系统跟不知道它存在),那你指定协程的实现原理是什么吗?
我们来聊聊协程的实现原理:
首先我们知道多个线程在一个单核CPU上进行并发,它的操作过程是,操作系统能调动的最小单位是线程,当操作系统触发多个线程到一个单核心的CPU上,接下来线程如何处理,怎样切换就不是操作系统能控制的了,那是由谁控制的,由硬件CPU,或者其他硬件控制的.它利用一个机制,将每一个线程切片,然后轮询将每一个线程的分片交给CPU处理.
但是你要知道,CPU同一时刻只能处理一个线程的分片.(那它是怎样将每一个分片对应到相应的线程的,就是通过CPU自己的寄存器,上下文,堆栈信息存储的.总之利用这些CPU会对应每一个线程的处理数据包.)也就是说,CPU也还是串行处理线程的任务的,只是它切换的特别快,让人类觉得是并行处理的.
那么这个协程有什么关系呢?当然有关系,协程正是python在代码中模仿单核CPU处理多线程的原理,利用代码造就了一个代码切换的机制,这个机制就是执行完有IO操作或者sleep这种操作的代码块后就切换到其他代码段,这样执行的时间就会缩短.因为串行中要等待处理后结果的操作时,这里用做执行其他代码了,等结果返回后在利用自有的寄存器\上下文\堆栈等信息对应到相应的代码段即可.
需要注意的是,单核CPU处理多线程时,硬件在不同线程间切换时要消耗时间.而协程技术所产生的切换动作是在一个线程中进行的.虽然协程的切换也要消耗时间,但是它不涉及到线程间的切换,只是CPU在处理一个线程代码时在代码段间不停的切换,所以协程的切换效率要比CPU单核处理多线程的效率还要高.消耗的时间还要短.
以上就是python协程实现的原理!
2.协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。 协程的好处: 无需线程上下文切换的开销
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点: 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序 那么我们还有一个疑问,协程和多线程哪个速度更快\执行效率更高.
我们知道Cpython有一个GIL全局解释性锁的特性.那么有这个锁导致的结果是,我有两种猜想:
1.python启用多线程后,比如8个线程,也许打到了2CPU的8个核心上,但是GIL会导致这些线程在同一时刻只有一个线程在执行.
2. python启用多线程后,python解释器层控制这在代码上生成的多线程轮询调用C语言的线程,实际上这些多线程,最终只是轮询调用c语言的一个线程接口.
上面两种对GIL实际的操作的猜测,我偏向于2.因为如果按照1中打到CPU的8个核心上的8个线程,后面就不在受操作系统原生线程的控制,而是硬件调度CPU处理线程里的数据包.所以全局解释性锁根本就控制不了.
那么如果是情况2 ,就意味着,GIL其实起到的左右和协程一样.那他们的效率应该也差不多吧.但是GIL这个貌似被看作是CPython的诟病的特性,应该效率比协程要低一些.另外多线程虽然根据cpython全局解释性锁的特性,最终轮询调用c语言的线程接口,但是在python内部还是维护着几个独立运行的线程任务,这几个线程任务独立运行,占用空间,但实现出来的效果确又和协程差不多,更不要说最终的效率还比协程低。这么一说我们当然知道协程的优势要远远大于多线程。
所以结论是,一般如果能用协程方式的程序用协程.并且用了协程后就不要在用多线程,可以用多进程加协程解决协程不能利用多核优势的缺点.
具体想想,协程应用场景和线程的应用场景应该有所不同,举个例子,如果像我们之前的例子,对一个全局变量,调用多线程进行递减,会有锁的问题.这个时候虽然也可以使用协程模块进行处理,但是我们知道协程是在单线程下运行的。所以你对一个全局变量进行更改,这即使程序中遇到io操作时,传给系统的io操作队列中,我先猜测操作系统执行io操作时会判断要操作内容的内存块的id,如果有一样的就串行执行,其他的都并行执行。
结论:协程是在单线程下进行的,所以是串行的。 当需要对一个全局变量或者对内存id一样的内存块进行更改时,不建议使用协程,因为意义不大。(结论是对的,只是对操作系统执行io的操作是猜测的,管他呢,好理解就行。) 使用yield实现协程操作例子
#!/usr/bin/env python3.
#__author__:ted.zhou
'''
使用yield实现协程的例子
'''
import time
import queue
def consumer(name):
print("--->starting eating baozi...")
while True:
new_baozi = yield
print("[%s] is eating baozi %s" % (name,new_baozi))
#time.sleep() def producer(): r = con.__next__() # python3.0里变成__next__(),python2.0是next()
r = con2.__next__()
n =
while n < :
time.sleep() # 模拟阻塞1秒
n +=
con.send(n)
con2.send(n)
print("\033[32;1m[producer]\033[0m is making baozi %s" %n ) if __name__ == '__main__':
con = consumer("c1")
con2 = consumer("c2")
p = producer()
我们看上面的例子,其实就是一个简单的协程首先,con,con2分别都执行了一部分代码,直到调用con.send(),con2.send()方法才继续后面的代码.
那么问题来了,如果生产者每做一个包子要花费1秒钟,那么相当于每次生产包子的时候产生1秒中的阻塞后,才能继续下面的代码,这就是前面提到的"进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序",影响到整个线程.
我们想实现,一旦碰到这种sleep()的或者其他IO操做,咱就切换到其他协程上(我们直到IO操作是交给操作系统的IO操作接口进行处理的,程序只要到操作系统注册一个IO任务,操作系统就会做后续的操作,最终把结果放到操作系统中等待返回给注册的程序),这样执行其他协程的代码,等其他协程有IO操作时,在切换到这个协程上取结果,这样就把本来要阻塞的1秒中给用上了.
那么到操作系统中注册一个IO操作怎么实现?不会~,通过这个普通的yield没有办法实现,yield只能实现一个简单的并发,但是一遇到阻塞怎样把阻塞丢给操作系统.yield不行.
用Greenlet可以实现~
Greenlet可以实现底层的阻塞的任务丢给操作系统的队列,具体实现细节后面再讲,它是一个第三方模块.我们不用它,我们就简单看一下就可以,我们要讲更好更高级的东西
Greenlet具体代码如下:
#!/usr/bin/env python
# -*- coding:utf- -*- from greenlet import greenlet def test1():
gr2.switch()
gr2.switch() def test2():
gr1.switch()
print gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr1.switch()
高级的来了~~
Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
安装gevent
$ cd /Library/Frameworks/Python.framework/Versions/3.5/bin/
$ pip3.5 install gevent
gevent模块实现协程一遇到IO操作和sleep切换的实例,代码如下:
#!/usr/bin/env python3.
#__author__:'ted.zhou'
'''
使用gevent模块创建协程代码实例
'''
import gevent
def foo():
print('\033[32;1mRunning in foo\033[0m')
gevent.sleep() # 这里sleep()就是为了掩饰gevent协程遇到IO或者sleep就切换的特性
print('\033[32;1mExplicit context switch to foo again\033[0m') def bar():
print('Explicit context to bar')
gevent.sleep()
print('Implicit context switch back to bar') gevent.joinall([ # gevent.joinall() 是等待所有执行完成的意思
gevent.spawn(foo), # gevent.spawn()是启动的意思
gevent.spawn(bar),
])
协程异步非阻塞:
上面的代码只是通过sleep的形式看切换的效果,那么我们现在通过gevent来看在但线程下并发的下载几个页面.
首先我们要了解一个简单的模块urllib,就是一个可以简单爬网页的一个模块.
在python3.0使用 urllib 中的urlopen 导入: from urllib import urlopen
在python2.0使用urllib2 中的urlopen 导入: from urllib2 import urlopen
代码如下:
#!/usr/bin/env python3.
#__author__:'ted.zhou'
'''
使用gevent 和 urllib 在但线程中并发的抓取几个页面
''' from gevent import monkey; monkey.patch_all()
import gevent
from urllib.request import urlopen def f(url):
print('GET: %s' % url)
resp = urlopen(url) #使用urlopen直接讲输入进来的URL进行下载
data = resp.read() # 把结果读取下来
print('%d bytes received from %s.' % (len(data), url)) #打印爬到的字节 # 使用gevent启动3个协程
gevent.joinall([
gevent.spawn(f, 'https://www.python.org/'), #调用f函数,后面是参数
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://github.com/'),
])
执行结果:
GET: https://www.python.org/
GET: https://www.yahoo.com/
GET: https://github.com/
bytes received from https://www.yahoo.com/.
bytes received from https://www.python.org/.
bytes received from https://github.com/. Process finished with exit code
从输出结果的顺序我们清楚的看到,一遇到爬页面的动作,就切换了.
运行过程如下:
当启动程序中的多个协程,遇到爬页面的请求,请求到操作系统注册一个IO请求后,就切换到第二,第三.
这三个请求发送到远端,远端通过网络返回数据.
远端通过网络返回给这台机器网络接口,网络接口数据进来的时候,网卡会通知CPU,对CPU产生一个中断请求,CPU接收到请求,就会通知操作系统说有数据来了,你要去接数据,操作系统就会把这个数据接回来,通知给当前具体的程序.程序内部会根据自己的寄存器\上下文\堆栈,把这个数据返回给对应的协程. 下面我们使用gevent实现一个更有用的代码实例
通过gevent实现单线程下的多socket并发
我们刚讲socket的,是一对一的,同时一个socket只能跟一个socket客户端,其他socket客户端都得排队等待.为了解决这个问题,实现一个多并发的效果,我们后来把单个socket改成了多线程的socketserver,改成多线程socket之后,是不是每一个客户端连过来的时候,socket server都会给这个连接分配一个新的线程跟这个客户端联系.这是低效的,为什么是低效的?
当有100个客户端连到多线程socketserver进行连接,但是连接之间的数据传输不多,那么就会产生上百个线程存在,但是实际用的不多.同时CPU还要不断的去检测socket客户端有没有传输数据.总体来说,开销很大,效率很低.
如果在线程下实现一个多socket,就像抓网页,在单线程下,但是每一个socket客户端过来我给你创建一个实例.不是每一个实例都是活跃,只有一小部分活跃.其他不活跃的就简单扫一遍,就略过,如果在线程下,只需要维护一个线程实现跟上百个socket通信.
如果想实现单线程下实现跟上百个socket客户端通信的效果.就必须解决如果一个客户端和我通信要10分钟,而其他实例要是也需要通信就得等待(阻塞)问题?
为了解决这个问题,客户端实例没有资格和socket服务端通信,在服务端前面前放一个纸箱子,服务端不断得轮询纸箱子看看有没有纸条,如果客户端需要和服务端说话,写一个纸条放入到纸箱子.当服务端看到纸条,就返回一个"已阅"得确认信息,这样每一个客户端传过来得服务端就都可以看到了.那这个但线程下得多socket就是这个效果.
代码中如何实现得呢?代码如下:
server side
#!/usr/bin/env python3.
#__author__:"ted.zhou"
'''
单线程下实现多socket,解决多线程socketserver开销大,效率低得问题
'''
import sys
import socket
import time
import gevent
from gevent import monkey # 非常有意思,python中得黑魔法
from gevent import socket # 导入的是gevent下的socket
monkey.patch_all() # 非常有意思,python中得黑魔法,我们写socket是不是很多是阻塞得,比如读IO,或网络接口都是阻塞得,而这个monkey.path_all()方法就使得代码一旦遇到阻塞就切换到其他线程.也就是标题所说的(非阻塞).
def server(port):
s = socket.socket()
s.bind(('0.0.0.0', port))
s.listen() # 最多可以监听500个连接
while True:
cli, addr = s.accept()
gevent.spawn(handle_request, cli) # 启动一个新的协程,把客户端的socket对象,调用
def handle_request(s):
try:
while True: # 一个循环,
data = s.recv() # 前面用了monkey.path_all()了,这里程序就不阻塞了,而是切换到其他线程了,这里就是回到调用它的server函数
print("recv:", data)
s.send(data)
if not data: # 如果没有数据
s.shutdown(socket.SHUT_WR) # 这个shutdown,就是把客户端连接过来产生的socket客户端对象销毁掉. except Exception as ex:
print(ex)
finally: s.close() # 把服务器跟这个客户端连接的实例关掉
if __name__ == '__main__':
server()
client side 代码如下:
#!/usr/bin/env python3.
#__author__:'ted.zhou'
'''
socket客户端代码,就是普通的客户端
''' import socket HOST = 'localhost' # The remote host
PORT = # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
msg = bytes(input(">>:"),encoding="utf8")
s.sendall(msg)
data = s.recv()
#print(data) # print('Received', repr(data))
print('Received', data.decode())
s.close()
同样你可以在client程序端,进行多线程的并发测试,看看server端能否正常应答,建议测试3000并发. 论事件驱动与异步IO
事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。 让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。
python2.0_s12_day9_协程&Gevent协程的更多相关文章
- 什么是协程?与线程和进程对比优劣在哪?gevent协程示例代码
协程 协程,又称微线程,纤程.英文名Coroutine..一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在 ...
- 协程--gevent模块(单线程高并发)
先恶补一下知识点,上节回顾 上下文切换:当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行.这种 ...
- python gevent 协程
简介 没有切换开销.因为子程序切换不是线程切换,而是由程序自身控制,没有线程切换的开销,因此执行效率高, 不需要锁机制.因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断 ...
- Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)
一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...
- Python Gevent协程自动切换IO
Gevent Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程. Gr ...
- (20)gevent协程
协程: 也叫纤程,协程是线程的一种实现,指的是一条线程能够在多任务之间来回切换的一 种实现,对于CPU.操作系统来说,协程并不存在 任务之间的切换会花费时间.目前电脑配置一般线程开到200会阻塞卡顿 ...
- python之协程gevent模块
Gevent官网文档地址:http://www.gevent.org/contents.html 进程.线程.协程区分 我们通常所说的协程Coroutine其实是corporate routine的缩 ...
- python---基础知识回顾(十)进程和线程(协程gevent:线程在I/O请求上的优化)
优点:使用gevent协程,可以更好的利用线程资源.(基于线程实现) 需求:使用一个线程,去请求多个网站的资源(注意,请求上会有延时)<实际上是去请求了大量的网站信息,我们使用了多线程,只不过每 ...
- gevent协程、select IO多路复用、socketserver模块 改造多用户FTP程序例子
原多线程版FTP程序:http://www.cnblogs.com/linzetong/p/8290378.html 只需要在原来的代码基础上稍作修改: 一.gevent协程版本 1. 导入geven ...
随机推荐
- 基于python的接口测试框架设计(二)配置一些参数及文件
基于python的接口测试框架设计(二)配置一些参数及文件 我这里需要基于我的项目配置的主要是登陆参数.以及baseURL ,把这些放在单独的文件里 毕竟导入的时候方便了一些 首先是url 图略 建 ...
- Python2 获取两日期之间的每一天
import datetime def getEveryDay(begin_date,end_date): date_list = [] begin_date = datetime.datetime. ...
- Zabbix添加自定义监控项(一)
前言:由于Zabbix提供的项目和模板有限,有时我们需要自定义监控项,下面以监控磁盘I/O使用率为例,创建自动发现规则,并配置图形. (1)Zabbix_agentd端自动发现脚本,zabbix要求返 ...
- 【WPF】C#代码动态添加控件的Margin属性
需求:一组按钮的数据是从服务器中Json数据发过来的,需要根据这个Json数据动态地添加这组按钮. 工具:使用http://www.newtonsoft.com/json来解析Json. 过程:C#代 ...
- 安装版的tomcat微信支付时报2字节的UTF-8序列的字节2无效
- 判断list为空的条件
if(list!=null&&!list.isEmpty()){ hql.append(" and (status = ? or sta ...
- 各种api接口
实用号码归属地查询(IP 地址,手机号码): 默认格式: http://api.liqwei.com/location/ (使用来访者的 IP 地址) 指定 IP 地址格式: http://api.l ...
- C++ c++与C语言的区别(struct类型的加强,函数-变量类型加强,bool类型)
//区别④:struct类型的加强(C++版本) #include<iostream> using namespace std; //C++中的struct是一个新类型的定义声明 //c+ ...
- 关于树莓派 BOOBS 安装之后的初级操作
以安装OpenCV 3.1.0为例 上一篇我们利用Raspberry 官方提供的工具 BOOBS安装了Raspbian 操作系统,下面让我们看一下如何简单的配置raspbian操作系统. 从树莓派官方 ...
- css的id选择器与thinkphp结合
<head> <style type="text/css"> #a2{ border:1px solid blue; width:140px; height ...