如何在一个python程序下的两个端口上的两个线程/进程中运行两个烧录服务器?

时间:2017-05-11 22:42:48

标签: python multithreading flask wsgi werkzeug

我正在编写一个python调试库,它在新线程中打开一个烧瓶服务器并提供有关它正在运行的程序的信息。当被调试的程序本身不是Web服务器时,这很好。但是,如果我尝试与另一个在调试模式下运行的烧瓶服务器同时运行它,那么事情就会中断。当我尝试访问第二台服务器时,结果在两台服务器之间交替。

以下是一个例子:

from flask.app import Flask
from threading import Thread

# app1 represents my debugging library

app1 = Flask('app1')

@app1.route('/')
def foo():
    return '1'

Thread(target=lambda: app1.run(port=5001)).start()

# Cannot change code after here as I'm not the one writing it

app2 = Flask('app2')

@app2.route('/')
def bar():
    return '2'

app2.run(debug=True, port=5002)

现在,当我在浏览器中访问http://localhost:5002/时,结果可能是12,而不是始终为2

使用multiprocessing.Process代替Thread会产生相同的结果。

这是怎么发生的,我该如何避免呢?使用flask / werkzeug / WSGI是不可避免的吗?我喜欢烧瓶的简单性,理想情况下我想继续使用它。如果那是不可能的,那么我可以使用的最简单的库/框架是什么,它不会干扰同时运行的任何其他Web服务器?如果可能的话,我也想使用线程而不是进程。

1 个答案:

答案 0 :(得分:1)

werkzeug的重新加载器(默认情况下在调试模式下使用)creates a new process使用subprocess.call,简化了它的作用:

new_environ = os.environ.copy()
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
subprocess.call([sys.executable] + sys.argv, env=new_environ, close_fds=False)

这意味着您的脚本被重新执行,如果它包含的所有内容都是app.run(),通常会很好,但在您的情况下,它会重新启动app1和app2,但现在都使用相同的端口,因为如果操作系统支持它在父进程中打开侦听端口,由子进程继承并在environment variable WERKZEUG_SERVER_FD is set处直接使用。

所以现在你有两个不同的应用程序以某种方式使用相同的套接字。

如果添加一些输出,您可以更好地看到这一点,例如:

from flask.app import Flask
from threading import Thread
import os

app1 = Flask('app1')

@app1.route('/')
def foo():
    return '1'

def start_app1():
    print("starting app1")
    app1.run(port=5001)

app2 = Flask('app2')

@app2.route('/')
def bar():
    return '2'

def start_app2():
    print("starting app2")
    app2.run(port=5002, debug=True)

if __name__ == '__main__':
    print("PID:", os.getpid())
    print("Werkzeug subprocess:", os.environ.get("WERKZEUG_RUN_MAIN"))
    print("Inherited FD:", os.environ.get("WERKZEUG_SERVER_FD"))
    Thread(target=start_app1).start()
    start_app2()

这打印例如:

PID: 18860
Werkzeug subprocess: None
Inherited FD: None
starting app1
starting app2
 * Running on http://127.0.0.1:5001/ (Press CTRL+C to quit)
 * Running on http://127.0.0.1:5002/ (Press CTRL+C to quit)
 * Restarting with inotify reloader
PID: 18864
Werkzeug subprocess: true
Inherited FD: 4
starting app1
starting app2
 * Debugger is active!

如果您将启动代码更改为

if __name__ == '__main__':
    if os.environ.get("WERKZEUG_RUN_MAIN")) != 'true':
        Thread(target=start_app1).start()
    start_app2()

然后它应该正常工作,重新加载器只重新加载app2。但是,它在一个单独的进程中运行,而不是在使用调试模式暗示的不同线程中运行。

要避免这种情况的黑客就是使用:

if __name__ == '__main__':
    os.environ["WERKZEUG_RUN_MAIN"] = 'true'
    Thread(target=start_app1).start()
    start_app2()

现在重新加载器认为它已经在子进程中运行并且没有启动新进程,所有内容都在同一进程中运行。重新加载不会起作用,我也不知道可能产生的其他副作用。