21.番外篇:Tornado的多进程管理分析---process.py代码解读

Tornado的多进程管理我们可以参看process.py这个文件。

在编写多进程的时候我们一般都用python自带的multiprocessing,使用方法和threading基本一致,只需要继承里面的Process类以后就可以编写多进程程序了,这次我们看看tornado是如何实现他的multiprocessing,可以说实现的功能不多,但是更加简单高效。

我们只看fork_process里面的代码:

01 global _task_id
02     assert _task_id is None
03     if num_processes is None or num_processes <= 0:
04         num_processes = cpu_count()
05     if ioloop.IOLoop.initialized():
06         raise RuntimeError("Cannot run in multiple processes: IOLoop instance "
07                            "has already been initialized. You cannot call "
08                            "IOLoop.instance() before calling start_processes()")
09     logging.info("Starting %d processes", num_processes)
10     children = {}

这一段很简单,就是在没有传入进程数的时候使用默认的cpu个数作为将要生成的进程个数。

01 def start_child(i):
02     pid = os.fork()
03     if pid == 0:
04         # child process
05         _reseed_random()
06         global _task_id
07         _task_id = i
08         return i
09     else:
10         children[pid] = i
11         return None

这是一个内函数,作用就是生成子进程。fork是个很有意思的方法,他会同时返回两种状态,为什么呢?其实fork相当于在原有的一条路(父进程)旁边又修了一条路(子进程)。如果这条路修成功了,那么在原有的路上(父进程)你就看到旁边来了另外一条路(子进程),所以也就是返回新生成的那条路的名字(子进程的pid),但是在另外一条路上(子进程),你看到的是自己本身修建成功了,也就返回自己的状态码(返回结果是0)。

所以if pid==0表示这时候cpu已经切换到子进程了,相当于我们在新生成的这条路上面做事(返回任务id);else表示又跑到原来的路上做事了,在这里我们记录下新生成的子进程,这时候children[pid]=i里面的pid就是新生成的子进程的pid,而 i 就是刚才在子进程里面我们返回的任务id(其实就是用来代码子进程的id号)。

1 for in range(num_processes):
2     id = start_child(i)
3     if id is not None:
4         return id

if id is not None表示如果我们在刚刚生成的那个子进程的上下文里面,那么就什么都不干,直接返回子进程的任务id就好了,啥都别想了,也别再折腾。如果还在父进程的上下文的话那么就继续生成子进程。

01 num_restarts = 0
02     while children:
03         try:
04             pid, status = os.wait()
05         except OSError, e:
06             if e.errno == errno.EINTR:
07                 continue
08             raise
09         if pid not in children:
10             continue
11         id = children.pop(pid)
12         if os.WIFSIGNALED(status):
13             logging.warning("child %d (pid %d) killed by signal %d, restarting",
14                             id, pid, os.WTERMSIG(status))
15         elif os.WEXITSTATUS(status) != 0:
16             logging.warning("child %d (pid %d) exited with status %d, restarting",
17                             id, pid, os.WEXITSTATUS(status))
18         else:
19             logging.info("child %d (pid %d) exited normally"id, pid)
20             continue
21         num_restarts += 1
22         if num_restarts > max_restarts:
23             raise RuntimeError("Too many child restarts, giving up")
24         new_id = start_child(id)
25         if new_id is not None:
26             return new_id

剩下的这段代码都是在父进程里面做的事情(因为之前在子进程的上下文的时候已经返回了,当然子进程并没有结束)。

pid, status = os.wait()的意思是等待任意子进程退出或者结束,这时候我们就把它从我们的children表里面去除掉,然后通过status判断子进程退出的原因。

如果子进程是因为接收到kill信号或者抛出exception了,那么我们就重新启动一个子进程,用的当然还是刚刚退出的那个子进程的任务号。如果子进程是自己把事情做完了才退出的,那么就算了,等待别的子进程退出吧。

我们看到在重新启动子进程的时候又使用了

1 if new_id is not None:
2     return new_id

主要就是退出子进程的空间,只在父进程上面做剩下的事情,不然刚才父进程的那些代码在子进程里面也会同样的运行,就会形成无限循环了,我没试过,不如你试试?

发布了13 篇原创文章 · 获赞 7 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章