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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章