1,通过设置app.run()的参数,来达到多进程的效果。看一下app.run的具体参数:
# 携程的第三方包-这里选择gevent, 当然你也可以选择eventlet
pip install gevent
# 具体的代码如下:
from flask import Flask
from gevent.pywsgi import WSGIServer
from gevent import monkey
# 将python标准的io方法,都替换成gevent中同名的方法,遇到io阻塞gevent自动进行协程切换
monkey.patch_all()
# 1.创建项目应用对象app
app = Flask(__name__)
# 初始化服务器
WSGIServer(("127.0.0.1", 5000), app).serve_forever()
# 来启动服务---这样就是以协程的方式运行项目,提高并发能力
python code.py
推荐】
pip install gunicorn
查看命令行选项: 安装gunicorn成功后,通过命令行的方式可以查看gunicorn的使用信息。
$gunicorn -h
# 使用gevent做异步(默认worker是同步的)
gunicorn -w 8 --bind 0.0.0.0:8000 -k 'gevent' 运行文件名称:Flask程序实例名
使用gunicorn + gevent 开启高并发
import multiprocessing
"""gunicorn+gevent 的配置文件"""
# 预加载资源
preload_app = True
# 绑定
bind = "0.0.0.0:5000"
# 进程数
workers = multiprocessing.cpu_count() * 2 + 1
# 线程数
threads = multiprocessing.cpu_count() * 2
# 等待队列最大长度,超过这个长度的链接将被拒绝连接
backlog = 2048
# 工作模式
# worker_class = "egg:meinheld#gunicorn_worker"
worker_class = "gevent"
# 最大客户客户端并发数量,对使用线程和协程的worker的工作有影响
worker_connections = 1200
# 进程名称
proc_name = 'gunicorn.pid'
# 进程pid记录文件
pidfile = 'app_run.log'
# 日志等级
loglevel = 'debug'
# 日志文件名
logfile = 'debug.log'
# 访问记录
accesslog = 'access.log'
# 访问记录格式
access_log_format = '%(h)s %(t)s %(U)s %(q)s'
# 运行方式 命令行
gunicorn -c gunicorn_config.py flask_server:app
前提在虚拟环境中安装meinheld:
pip install meinheld
import multiprocessing
"""gunicorn+meinheld 的配置文件"""
# 预加载资源
preload_app = True
# 绑定
bind = "0.0.0.0:5000"
# 进程数: cup数量 * 2 + 1
workers = multiprocessing.cpu_count() * 2 + 1
# 线程数 cup数量 * 2
threads = multiprocessing.cpu_count() * 2
# 等待队列最大长度,超过这个长度的链接将被拒绝连接
backlog = 2048
# 工作模式
worker_class = "egg:meinheld#gunicorn_worker"
# 最大客户客户端并发数量,对使用线程和协程的worker的工作有影响
worker_connections = 1200
# 进程名称
proc_name = 'gunicorn.pid'
# 进程pid记录文件
pidfile = 'app_run.log'
# 日志等级
loglevel = 'debug'
# 日志文件名
logfile = 'debug.log'
# 访问记录
accesslog = 'access.log'
# 访问记录格式
access_log_format = '%(h)s %(t)s %(U)s %(q)s'
# 运行方式 命令行
gunicorn -c gunicorn_config.py flask_server:app
拓展
概念:协程就是协同工作的程序,不是进程也不是线程 理解成--不带返回值的函数调用。
Coroutine:协程,又称微线程,纤程。
协程的这种“挂起”和“唤醒”机制实质上是将一个过程切分成了若干个子过程,给了我们一种以扁平的方式来使用事件回调模型。优点:共享进程的上下文,一个进程可以创建百万,千万的coroutine。
python中的yield和第三方库greenlet,都可以实现协程。
greenlet 提供了在协程中直接切换控制权的方式,比生成器(yield)更加灵活、简洁。
历史遗留问题—GIL锁
1.线程安全是在多线程的环境下,线程安全能够保证多个线程同时执行时程序依旧运行正确,而且要保证对于共享的数据,可以由多个线程存取,但是同一时刻只能有一个线程进行存取。每一个interpreter进程,只能同时仅有一个线程来执行,获得相关的锁,存取相关的资源。那么很容易就会发现,如果一个interpreter进程只能有一个线程来执行,多线程的并发则成为不可能,即使这几个线程之间不存在资源的竞争。
2.所以虽然 CPython的线程库直接封装操作系统的原生线程,但CPython进程做为一个整体同一时间只会有一个获得了GIL的线程在跑,其它的线程都处于等待状态等着 GIL的释放。所以只能使用cpu单核。这也是python多线程被人诟病的原因。
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。
1. select(线程不安全):它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
2. poll(线程不安全):它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制
3. epoll(线程安全):epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
多进程 + 协程 + callback(io多路复用做事件驱动)
gevent = greenlet + python.monkey(底层使用 libevent 时间复杂度: O(N * logN))
meinheld = greenlet + picoev (时间复杂度: O(N) )
eventlet
meinheld和gevent都能实现异步,但是测评中meinheld比gevent的性能好很多,不过因为meinheld支持的比较少,一般都是配合gunicorn使用的。下面分析一下meinheld和gevent性能差距主要原因,分别使用的是picoev和lievent。
# libevent
主要实现:使用堆(优先队列)作为timer事件的算法(nlogn),IO和信号的实现均使用了双向队列(用链表实现)。
时间复杂度: O(N * logN)
# picoev
picoev:根据作者简介,主要优化有两点。
1. 主要是考虑是fd(file descriptors)在unix中是用比较小的正整数表示的,那么把fd的相关信息,全部存储在一个array中,这样使得查找快速,在操作socket状态时会更加的快。
2. 第二点是对于timer事件的算法优化,通过环形缓冲区(128)和bit vector实现查看部分源码可以看出,主要实现是每个时间点对应的是缓冲区的一个位置,每个缓存区使用bit vector 表示fd的数值,相当于一种hash映射所以时间复杂度为(o(n)),n为那个缓存区所存的fd数量。
时间复杂度: O(N)
性能: picoev > libevent
程序发送阻塞的时候切换
读磁盘
读写文件
网络io操作
收发http请求