- 高并发的瓶颈在哪?
根据大多数人对高并发的经验得知,大多数时候在IO上面,这里说得是大多数,不是说绝对。
因为大多数时候业务本质上都是从数据库或者其他存储上读取内容,然后根据一定的逻辑,将数据返回给用户,比如大多数web内容。而大多数逻辑的交互都算不上计算量多大的逻辑,CPU的速度要远远高于内存IO,磁盘IO,网络IO, 而这些IO中网络IO最慢。
当并发高到一定的程度,根据业务的不同,比如计算密集,IO密集,或两者皆有,因此瓶颈可能出在计算上面或者IO上面,又或两者兼有。
解释:IO密集型:简单的可以理解为对某些应用的输入输出,最常见web应用
CPU密集型: 要进行大量的计算,消耗CPU资源 - Python怎么处理高并发?
使用协程,事件循环,高效IO模型(比如多路复用),三者缺一不可
很多文章都是说协程,最后告诉我一些协程库或者asyncio用来说明协程的威力,但是一切还是得从生成器说起,因为asyncio或者大多数协程库内部也是通过生成器实现的。
如果对事件循环或者说多路复用的经验,也许能够隐隐察觉到微妙的感觉。 这个微妙的感觉是,是否可以将IO操作yield出来?由事件循环调度, 如果能get到这个微妙的感觉,那么你已经知道协程高并发的秘密了.
'''
说到协程之前得先明白生成器,生成器的定义很抽象,百度百科: 生成器是一次生成一个值的特殊类型函数。可以将其视为可恢复函数。
生成器的关键字在yield
'''
def gen_func():
yield 1
yield 2
yield 3
if __name__ == '__main__':
gen = gen_func()
print(next(gen))
print(next(gen))
print(next(gen))
返回结果为:
1
2
3
'''
每次执行到yield时,会跳出去并将yield关键字后面的内容返回给调用者。下一次有别的调用者再次调用生成器时,会先恢复生成器上次的机器状态,再接着执行指导遇到yield或者元素迭代完毕,这就是生成器的实现原理。
'''
那么协程是怎么回事呢,协程就是多个生成器函数之间的调用,这就是协程在底层实现的方法
- io模型
Linux平台一共有五大IO模型,每个模型有自己的优点与确定。根据应用场景的不同可以使用不同的IO模型。
1.同步IO,是效率最低的模型了,处理完一个连接才能处理下一个。
2.非阻塞式IO,不会阻塞后面的代码,一般通过while循环,耗费大量的CPU
3.多路复用,当前最流行,使用最广泛的高并发方案。
4.信号驱动式IO,很偏门的一个IO模型,不曾遇见过使用案例。
5.异步非阻塞IO,理论上比多路复用更快,因为少了一次调用,但是实际使用并没有比多路复用快非常多 - 事件循环
上面的IO模型能够解决IO的效率问题,但是实际使用起来需要一个事件循环驱动协程去处理IO。
# 创建一个selctor对象
# 在不同的平台会使用不同的IO模型,比如Linux使用epoll
# 使用select调度IO
sel = selectors.DefaultSelector()
# 回调函数,用于接收新连接
def accept(sock, mask):
conn, addr = sock.accept() # Should be ready
print('accepted', conn, 'from', addr)
conn.setblocking(False) #设置socket的阻塞或非阻塞模式
sel.register(conn, selectors.EVENT_READ, read)
# 回调函数,用户读取client用户数据
def read(conn, mask):
data = conn.recv(1000) # Should be ready
if data:
print('echoing', repr(data), 'to', conn)
conn.send(data)
else:
print('closing', conn)
sel.unregister(conn) # 注销描述符
conn.close()
# 创建一个非堵塞的socket
sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)
# 一个事件循环,用于IO调度
# 当IO可读或者可写的时候, 执行事件所对应的回调函数
def loop():
while True:
events = sel.select()
for key, mask in events:
callback = key.data
callback(key.fileobj, mask)
if __name__ == '__main__':
loop()
# 上面代码中loop函数对应事件循环,它要做的就是一遍一遍的等待IO,然后调用事件的回调函数.
- 小结
Python也实现了优秀高并发异步IO框架,比如tornado,gevent等等
Python之所以能够处理网络IO高并发,是因为借助了高效的IO模型,能够最大限度的调度IO,然后事件循环使用协程处理IO,协程遇到IO操作就将控制权抛出,那么在IO准备好之前的这段事件,事件循环就可以使用其他的协程处理其他事情,然后协程在用户空间,并且是单线程的,所以不会像多线程,多进程那样频繁的上下文切换,因而能够节省大量的不必要性能损失。