前言
这里先引用一下百度百科的定义.
并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行
里面的一个时间段内说明非常重要,这里假设这个时间段是一秒,所以本文指的并发是指服务器在一秒中处理的请求数量,即rps,那么rps高,本文就认为高并发.
啥?这不是你认为的高并发, 出门左转。
操作系统到底在干啥?
如果由笔者来概括,操作系统大概做了两件事情,计算与IO,任何具体数学计算或者逻辑判断,或者业务逻辑都是计算,而网络交互,磁盘交互,人机之间的交互都是IO。
高并发的瓶颈在哪?
根据笔者经验,大多数时候在IO上面。注意,这里说得是大多数,不是说绝对。
因为大多数时候业务本质上都是从数据库或者其他存储上读取内容,然后根据一定的逻辑,将数据返回给用户,比如大多数web内容。而大多数逻辑的交互都算不上计算量多大的逻辑,CPU的速度要远远高于内存IO,磁盘IO,网络IO, 而这些IO中网络IO最慢。
在根据上面的笔者对操作系统的概述,当并发高到一定的程度,根据业务的不同,比如计算密集,IO密集,或两者皆有,因此瓶颈可能出在计算上面或者IO上面,又或两者兼有。
而本文解决的高并发,是指IO密集的高并发瓶颈,因此,计算密集的高并发并不在本文的讨论范围内。
为了使本文歧义更少,这里的IO主要指网络IO.
Python怎么处理高并发?
使用协程, 事件循环, 高效IO模型(比如多路复用,比如epoll), 三者缺一不可。
很多时候,笔者看过的文章都是说协程如何如何,最后告诉我一些协程库或者asyncio用来说明协程的威力,最终我看懂了协程,却还是不知道它为啥能高并发,这也是笔者写本文的目的。
但是一切还是得从生成器说起,因为asyncio或者大多数协程库内部也是通过生成器实现的。
注意上面的三者缺一不可。 如果只懂其中一个,那么你懂了三分之一,以此类推,只有都会了,你才知道为啥协程能高并发。
生成器
生成器的定义很抽象,现在不懂没关系,但是当你懂了之后回过头再看,会觉得定义的没错,并且准确。下面是定义
摘自百度百科: 生成器是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。
关于生成器的内容,本文着重于生成器实现了哪些功能,而不是生成器的原理及内部实现。
yield
简单例子如下
def gen_func(): yield 1 yield 2 yield 3if __name__ == '__main__': gen = gen_func() for i in gen: print(i)output:123
上面的例子没有什么稀奇的不是吗?yield像一个特殊的关键字,将函数变成了一个类似于迭代器的对象,可以使用for循环取值。
send, next
协程自然不会这么简单,python协程的目标是星辰大海,从上面的例之所以get不到它的野心,是因为你没有试过send, next两个函数。
首先说next
def gen_func(): yield 1 yield 2 yield 3if __name__ == '__main__': gen = gen_func() print(next(gen)) print(next(gen)) print(next(gen))output:123
next的操作有点像for循环,每调用一次next,就会从中取出一个yield出来的值,其实还是没啥特别的,感觉还没有for循环好用。
不过,不知道你有没有想过,如果你只需要一个值,你next一次就可以了,然后你可以去做其他事情,等到需要的时候才回来再次next取值。
就这一部分而言,你也许知道为啥说生成器是可以暂停的了,不过,这似乎也没什么用,那是因为你不知到时,生成器除了可以抛出值,还能将值传递进去。
接下来我们看send的例子。
def gen_func(): a = yield 1 print("a: