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的诟病的特性,应该效率比协程要低一些.        所以结论是,一般如果能用协程方式的程序用协程.并且用了协程后就不要在用多线程,因为两个一样的东西,没意义.用线程反而能更好的利用多核的硬件优势.        具体想想,协程应用场景和线程的应用场景应该有所不同,举个例子,如果像我们之前的例子,对一个全局变量,调用多线程进行递减,会有锁的问题.但是这时候你可不能用协程的方式,调用同一个函数,对同一个变量进行更改.        如果你调用了,那同样还是有这么一个锁的问题存在,因为你在有IO操作后,切换到下一个对同一个变量进行递减的操作.这结果还不是乱套.所以我觉得协程应用场景应该是多个不同函数调用的时候使用,减少对有IO操作和sleep这样操作等待的时间.

        使用yield实现协程操作例子  
 1   
 2             #!/usr/bin/env python3.5
 3             #__author__:ted.zhou
 4             ‘‘‘
 5             使用yield实现协程的例子
 6             ‘‘‘
 7             import time
 8             import queue
 9             def consumer(name):
10                 print("--->starting eating baozi...")
11                 while True:
12                     new_baozi = yield
13                     print("[%s] is eating baozi %s" % (name,new_baozi))
14                     #time.sleep(1)
15
16             def producer():
17
18                 r = con.__next__()  # python3.0里变成__next__(),python2.0是next()
19                 r = con2.__next__()
20                 n = 0
21                 while n < 5:
22                     time.sleep(1) # 模拟阻塞1秒
23                     n +=1
24                     con.send(n)
25                     con2.send(n)
26                     print("\033[32;1m[producer]\033[0m is making baozi %s" %n )
27
28
29             if __name__ == ‘__main__‘:
30                 con = consumer("c1")
31                 con2 = consumer("c2")
32                 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具体代码如下:
 1             #!/usr/bin/env python
 2             # -*- coding:utf-8 -*-
 3
 4
 5             from greenlet import greenlet
 6
 7
 8             def test1():
 9                 print 12
10                 gr2.switch()
11                 print 34
12                 gr2.switch()
13
14
15             def test2():
16                 print 56
17                 gr1.switch()
18                 print 78
19
20             gr1 = greenlet(test1)
21             gr2 = greenlet(test2)
22             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切换的实例,代码如下:
 1             #!/usr/bin/env python3.5
 2             #__author__:‘ted.zhou‘
 3             ‘‘‘
 4             使用gevent模块创建协程代码实例
 5             ‘‘‘
 6             import gevent
 7             def foo():
 8                 print(‘\033[32;1mRunning in foo\033[0m‘)
 9                 gevent.sleep(0)  # 这里sleep(0)就是为了掩饰gevent协程遇到IO或者sleep就切换的特性
10                 print(‘\033[32;1mExplicit context switch to foo again\033[0m‘)
11
12             def bar():
13                 print(‘Explicit context to bar‘)
14                 gevent.sleep(0)
15                 print(‘Implicit context switch back to bar‘)
16
17             gevent.joinall([         # gevent.joinall() 是等待所有执行完成的意思
18                 gevent.spawn(foo),   # gevent.spawn()是启动的意思
19                 gevent.spawn(bar),
20             ])
        协程异步非阻塞:        上面的代码只是通过sleep的形式看切换的效果,那么我们现在通过gevent来看在但线程下并发的下载几个页面.        首先我们要了解一个简单的模块urllib,就是一个可以简单爬网页的一个模块.        在python3.0使用 urllib 中的urlopen  导入: from urllib import urlopen        在python2.0使用urllib2 中的urlopen  导入: from urllib2 import urlopen        代码如下:
 1             #!/usr/bin/env python3.5
 2             #__author__:‘ted.zhou‘
 3             ‘‘‘
 4             使用gevent 和 urllib 在但线程中并发的抓取几个页面
 5             ‘‘‘
 6
 7             from gevent import monkey; monkey.patch_all()
 8             import gevent
 9             from  urllib.request import urlopen
10
11             def f(url):
12                 print(‘GET: %s‘ % url)
13                 resp = urlopen(url)     #使用urlopen直接讲输入进来的URL进行下载
14                 data = resp.read()      # 把结果读取下来
15                 print(‘%d bytes received from %s.‘ % (len(data), url)) #打印爬到的字节
16
17             # 使用gevent启动3个协程
18             gevent.joinall([
19                     gevent.spawn(f, ‘https://www.python.org/‘), #调用f函数,后面是参数
20                     gevent.spawn(f, ‘https://www.yahoo.com/‘),
21                     gevent.spawn(f, ‘https://github.com/‘),
22             ])
23         执行结果:
24             GET: https://www.python.org/
25             GET: https://www.yahoo.com/
26             GET: https://github.com/
27             451842 bytes received from https://www.yahoo.com/.
28             47381 bytes received from https://www.python.org/.
29             25540 bytes received from https://github.com/.
30
31             Process finished with exit code 0
        从输出结果的顺序我们清楚的看到,一遇到爬页面的动作,就切换了.        运行过程如下:            当启动程序中的多个协程,遇到爬页面的请求,请求到操作系统注册一个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就是这个效果.        代码中如何实现得呢?代码如下:
 1         server side
 2             #!/usr/bin/env python3.5
 3             #__author__:"ted.zhou"
 4             ‘‘‘
 5             单线程下实现多socket,解决多线程socketserver开销大,效率低得问题
 6             ‘‘‘
 7             import sys
 8             import socket
 9             import time
10             import gevent
11             from gevent import monkey   # 非常有意思,python中得黑魔法
12             from gevent import socket   # 导入的是gevent下的socket
13             monkey.patch_all()          # 非常有意思,python中得黑魔法,我们写socket是不是很多是阻塞得,比如读IO,或网络接口都是阻塞得,而这个monkey.path_all()方法就使得代码一旦遇到阻塞就切换到其他线程.也就是标题所说的(非阻塞).
14             def server(port):
15                 s = socket.socket()
16                 s.bind((‘0.0.0.0‘, port))
17                 s.listen(500)           # 最多可以监听500个连接
18                 while True:
19                     cli, addr = s.accept()
20                     gevent.spawn(handle_request, cli)  # 启动一个新的协程,把客户端的socket对象,调用
21             def handle_request(s):
22                 try:
23                     while True:                 # 一个循环,
24                         data = s.recv(1024)     # 前面用了monkey.path_all()了,这里程序就不阻塞了,而是切换到其他线程了,这里就是回到调用它的server函数
25                         print("recv:", data)
26                         s.send(data)
27                         if not data:            # 如果没有数据
28                             s.shutdown(socket.SHUT_WR)  # 这个shutdown,就是把客户端连接过来产生的socket客户端对象销毁掉.
29
30                 except Exception as  ex:
31                     print(ex)
32                 finally:
33
34                     s.close()       # 把服务器跟这个客户端连接的实例关掉
35             if __name__ == ‘__main__‘:
36                 server(8001)
        client side 代码如下:
 1             #!/usr/bin/env python3.5
 2             #__author__:‘ted.zhou‘
 3             ‘‘‘
 4             socket客户端代码,就是普通的客户端
 5             ‘‘‘
 6
 7             import socket
 8
 9             HOST = ‘localhost‘    # The remote host
10             PORT = 8001           # The same port as used by the server
11             s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
12             s.connect((HOST, PORT))
13             while True:
14                 msg = bytes(input(">>:"),encoding="utf8")
15                 s.sendall(msg)
16                 data = s.recv(1024)
17                 #print(data)
18
19                 # print(‘Received‘, repr(data))
20                 print(‘Received‘, data.decode())
21             s.close()
        同样你可以在client程序端,进行多线程的并发测试,看看server端能否正常应答,建议测试3000并发.

    论事件驱动与异步IO        事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常见的编程范式是(单线程)同步以及多线程编程。

        让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着时间的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的时间已经用灰色框标示出来了。
时间: 2024-08-24 21:17:57

python2.0_s12_day9_协程&Gevent协程的相关文章

python gevent 协程

简介 没有切换开销.因为子程序切换不是线程切换,而是由程序自身控制,没有线程切换的开销,因此执行效率高, 不需要锁机制.因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多 Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程. yield 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁. 如果改用协程,生产者生产消息后,直接通过y

17、第七周-网络编程 - 协程概念介绍、协程gevent模块并发爬网页

协程,又称微线程,纤程.什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈.因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置. 协程的好处: 无需线程上下文切换的开销 无需原子操作锁定及同步的开销(注解:"原子操作(atomic operation)是不需要synchr

协程:gevent模块,遇到i/o自动切换任务 038

协程 : gevent模块,遇到io自动切换任务 from gevent import monkey;monkey.patch_all() # 写在最上面 这样后面的所有阻塞就全部能够识别了 import gevent # 直接导入即可 from threading import current_thread import time def eat(name): print('%seat1'% name) # gevent.sleep(2) time.sleep(2) # 加上monkey就能

网络编程之协程——gevent模块

网络编程之协程--gevent模块 gevent模块 安装 pip3 install gevent Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程. Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度. #用法 g1=gevent.spawn(func,1,,2,3,x=4,y=5)创建一个协程对象g1,spawn括号内第一个参数是函数名,如e

tcp_server_协程gevent版本

#!/usr/bin/env python # -*- coding:utf-8 -*- # @Time : 2020/1/23 1:50 # @Author : liuyan # @File : test5_tcp_server_5协程gevent版本.py # @Software: PyCharm import gevent from gevent import monkey; monkey.patch_all() #使用此方法,会将代码中检查一遍,如有time.sleep()等延时方法,会

进程池与线程池、协程、协程实现TCP服务端并发、IO模型

进程池与线程池.协程.协程实现TCP服务端并发.IO模型 一.进程池与线程池 1.线程池 ''' 开进程开线程都需要消耗资源,只不过两者比较的情况下线程消耗的资源比较少 在计算机能够承受范围内最大限度的利用计算机 什么是池? 在保证计算机硬件安全的情况下最大限度的利用计算机 池其实是降低了程序的运行效率,但是保证了计算机硬件的安全 (硬件的发展跟不上软件的速度) ''' from concurrent.futures import ThreadPoolExecutor import time p

Lua的协程和协程库详解

我们首先介绍一下什么是协程.然后详细介绍一下coroutine库,然后介绍一下协程的简单用法,最后介绍一下协程的复杂用法. 一.协程是什么? (1)线程 首先复习一下多线程.我们都知道线程——Thread.每一个线程都代表一个执行序列. 当我们在程序中创建多线程的时候,看起来,同一时刻多个线程是同时执行的,不过实质上多个线程是并发的,因为只有一个CPU,所以实质上同一个时刻只有一个线程在执行. 在一个时间片内执行哪个线程是不确定的,我们可以控制线程的优先级,不过真正的线程调度由CPU的调度决定.

关于进程、线程、协程、管程、纤程、超线程的对比理解

1.进程 任务.作业(Job,Task,Schedule):在进程的概念出现之前,进程有着这样的称谓. 为了使多个程序能够并发(同一时刻只有一个在运行,但感觉起来像多个同时运行:并行(同一时刻真的多个在运行,不是感觉像多个))的执行,操作系统需要一个结构来抽象和表示这个程序的运行. 特性: 1.进程是操作系统对一个正在运行的程序的一种抽象结构. 2.进程是指在操作系统中能独立运行并作为资源分配的基本单位,由一组机器指令.数据和堆栈等组成的能独立运行的活动实体. 3.操作系统可以同时运行多个进程,

Linux高性能网络:协程系列05-协程实现之原语操作

目录 Linux高性能网络:协程系列01-前言 Linux高性能网络:协程系列02-协程的起源 Linux高性能网络:协程系列03-协程的案例 Linux高性能网络:协程系列04-协程实现之工作原理 Linux高性能网络:协程系列05-协程实现之原语操作 Linux高性能网络:协程系列06-协程实现之切换 Linux高性能网络:协程系列07-协程实现之定义 Linux高性能网络:协程系列08-协程实现之调度器 Linux高性能网络:协程系列09-协程性能测试 [Linux高性能网络:协程系列10