Python之路,Day9 - 异步IO数据库队列缓存
本节内容
Gevent协程
SelectPollEpoll异步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实现协程操作例子
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("