tornado多进程启动时,采用的是fork的方式。
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间(百度百科上的错误)如果采用copy on write技术,在存储的内容未发生修改前父子进程是共享同一份存储空间。
UNIX将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。在不同的UNIX (Like)系统下,我们无法确定fork之后是子进程先运行还是父进程先运行,这依赖于系统的实现。所以在移植代码的时候我们不应该对此作出任何的假设。
上述内容摘自百度百科对fork函数的说明,在调用fork函数产生的子进程将会获得父进程的数据空间、堆、栈等资源的“副本”,这里的“副本”有可能是存储空间的同一份。父进程打开的文件句柄,socket等资源也会被原封不动的拷备给子进程。
最常出现的问题就是logging模块,python的logging模块是线程安全的,但不是进程安全的。如果在fork子进程之前开始实例化logging模块,各个子进程都会共享了同一个fd,所以会出现日志错乱的问题。为了避免这个问题,各个进程打都输出到独立的文件,延后Logger的实例化。
sockets = tornado.netutil.bind_sockets(int(options.port))
fork_id = tornado.process.fork_processes(int(redis_info.num_processes))
server = tornado.httpserver.HTTPServer(application)
server.add_sockets(sockets)
#实现logger init方式,接收fork_id来控制文件名
logger.init(fork_id)
另一点就是socket的复用导致的问题,实现了一个python版本的dubbo客户端,连接发送方式如下。在使用过程中,fork进程之前实例化了一个client, 并保持了一个socket。
def