簡介
程序每次執行時,操作系統都會創建一個新進程來運行程序指令。進程中可調用os.fork,要求操作系統新建一個子進程.[Windowsc系統中,os模塊沒有os.fork函數]。
每個進程都有一個不重復的進程ID號。或稱pid,它對進程進行標識。子進程與父進程完全相同,子進程從父進程繼承了多個值的拷貝。如全局變量和環境變量。fork后,子進程接收返回值0,而父進程接收子進程的pid作為返回值
os.
fork
()
Fork a child process. Return 0
in the child and the child’s process id in the parent. If an error occurs OSError is raised.
Note that some platforms including FreeBSD <= 6.3 and Cygwin have known issues when using fork() from a thread.
Availability: Unix > 僅支持基於Unix核心的系統
(一)fork函數在子進程中返回0,在父進程中返回子進程的id:
-
os.getpid() 返回進程pid
-
os.getppid() 返回父進程pid
# -*-coding:utf-8-*- import os import time print('before calling') p = os.fork() # 主進程,子進程同時向下執行 print('after calling') if p == 0: print('執行子進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: print('執行主進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
[root@192 ~]# python fork.py
before calling
after calling
執行主進程, pid=1629 ppid=1572 p=1630
after calling
執行子進程, pid=1630 ppid=1629 p=0
結論:調用os.fork()之后,主進程和子進程同時執行該行以下的代碼,子進程中fork函數返回0,父進程中返回1630,即子進程的pid.
再看下面的代碼結果:
# -*-coding:utf-8-*- import os import time print('before calling') p = os.fork() # 主進程,子進程同時向下執行 print('after calling') if p == 0: print('執行子進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) time.sleep(1) print('執行子進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: print('執行主進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
[root@192 ~]# python fork.py
before calling
after calling
執行主進程, pid=1648 ppid=1572 p=1649
after calling
執行子進程, pid=1649 ppid=1648 p=0
[root@192 ~]# 執行子進程, pid=1649 ppid=1 p=0
子進程先打印一段信息,再睡一秒,再打印一段信息,在子進程輸出的兩次信息中,ppid分別是1648和1。—— 問1:ppid怎么變了?
接下來進行探討:
(二)fork()開啟進程,主進程執行結束后,不會等待子進程:
實踐:后台執行下面的代碼,讓主進程睡五秒,子進程睡十秒:
# -*-coding:utf-8-*- import os import time p = os.fork() if p == 0: time.sleep(10) print('執行子進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: time.sleep(5) print('執行主進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p))
[root@192 ~]# python fork.py & ### 后台執行python代碼
[1] 1693
### 五秒前,查看進程信息:主進程為1693,子進程為1694
[root@192 ~]# ps aux | grep fork.py
root 1693 0.0 0.1 125432 4592 pts/0 S 21:23 0:00 python fork.py
root 1694 0.0 0.0 125432 2748 pts/0 S 21:23 0:00 python fork.py
root 1696 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
[root@192 ~]# 執行主進程, pid=1693 ppid=1572 p=1694 (此為程序打印信息,說明主進程已執行完)
[1]+ Done python fork.py
### 五秒后,主進程執行完畢,查看進程信息:只剩子進程1694
[root@192 ~]# ps aux | grep fork.py
root 1694 0.0 0.0 125432 2748 pts/0 S 21:23 0:00 python fork.py
root 1698 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
[root@192 ~]# 執行子進程, pid=1694 ppid=1 p=0 (此為程序打印信息,說明子進程已執行完。 *注意,這里ppid是1)
### 十秒后,子進程執行完畢,子進程結束
[root@192 ~]# ps aux | grep fork.py
root 1708 0.0 0.0 112704 980 pts/0 S+ 21:23 0:00 grep --color=auto fork.py
現象:五秒前,兩個進程都在執行,五秒后,主進程結束,只剩一個子進程(說明父進程沒有等待子進程),十秒后,子進程結束
結論:父進程執行結束后不等待子進程
問1的解釋:子進程第一次打印時,剛好父進程還未結束,還可以獲取到父進程ppid,因此第一次打印的ppid是父進程的pid,當睡了一秒以后,父進程早就執行完了,溜了,沒有等待子進程,因此子進程交給了init進程, ppid就變成1.
(三)僵屍進程:
若子進程比父進程先結束,而父進程又沒有回收子進程,釋放子進程占用的資源,此時子進程將成為一個僵屍進程。
有什么壞處? 如果大量的產生僵屍進程,將因為沒有可用的進程號而導致系統不能產生新的進程. 此即為僵屍進程的危害,應當避免。
僵屍進程的避免:
- 父進程通過os.wait()和os.waitpid()等函數等待子進程結束,這會導致父進程掛起。
- 如果父進程很忙,那么可以用signal函數為SIGCHLD安裝handler,因為子進程結束后, 父進程會收到該信號,可以在handler中調用wait回收。
- 如果父進程不關心子進程什么時候結束,那么可以用signal(SIGCHLD,SIG_IGN) 通知內核,自己對子進程的結束不感興趣,那么子進程結束后,內核會回收, 並不再給父進程發送信號。
- 還有一些技巧,就是fork兩次,父進程fork一個子進程,然后繼續工作,子進程fork一 個孫進程后退出,那么孫進程被init接管,孫進程結束后,init會回收。不過子進程的回收 還要自己做。
子進程變成僵屍進程,是因為父進程先執行完,沒有替子進程收屍。而wait()並不是用來收屍的,只是防止父進程先於子進程退出;如果父進程先退出,會使子進程成為僵屍進程,這時候子進程的收屍就由1號init進程來回收。
主進程通過調用os.wait()等待子進程結束:
# -*-coding:utf-8-*- import os import time p = os.fork() if p == 0: time.sleep(10) print('執行子進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) else: time.sleep(5) print('執行主進程, pid={} ppid={} p={}'.format(os.getpid(), os.getppid(), p)) os.wait()
[root@192 ~]# python fork.py & # 后台執行python代碼
### 五秒前,查看進程信息:主進程為1751,子進程為1752
[1] 1751
[root@192 ~]# ps aux | grep fork.py
root 1751 0.5 0.1 125432 4588 pts/0 S 21:29 0:00 python fork.py
root 1752 0.0 0.0 125432 2748 pts/0 S 21:29 0:00 python fork.py
root 1754 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
[root@192 ~]# 執行主進程, pid=1751 ppid=1572 p=1752 (此為程序打印信息,說明主進程已執行到os.wait())
### 五秒后,主程序打印了信息並調用了os.wait(),查看進程信息:主進程為1751,子進程為1752,主進程沒有結束
[root@192 ~]# ps aux | grep fork.py
root 1751 0.1 0.1 125436 4588 pts/0 S 21:29 0:00 python fork.py
root 1752 0.0 0.0 125432 2748 pts/0 S 21:29 0:00 python fork.py
root 1756 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
[root@192 ~]# 執行子進程, pid=1752 ppid=1751 p=0 (此為程序打印信息,說明子進程已執行完。 *注意,這里ppid不是1)
[1]+ Done python fork.py
### 十秒后,子進程執行完畢,子進程結束,父進程隨着子進程的結束而結束
[root@192 ~]# ps aux | grep fork.py
root 1758 0.0 0.0 112704 980 pts/0 S+ 21:29 0:00 grep --color=auto fork.py
現象:五秒前,兩個進程都在執行,五秒后,主進程執行完成並調用了os.wait(),等待子進程結束,十秒后,子進程結束,父進程也隨之結束
結論:父進程可調用os.wait()等待子進程結束。 *沒有子進程就調用os.wait()會拋異常: OSError: [Errno 10] No child processes
(四)進程間資源獨立:
實踐:在fork()函數前定義一個變量,在子進程中修改值,再從主進程中查看變量是否被修改:
# -*-coding:utf-8-*- import os import time variable = [] p = os.fork() if p == 0: variable.append(1) print('子進程 variable_id={}'.format(id(variable))) print('子進程 variable={}'.format(variable)) else: time.sleep(1) # 睡一秒,讓子進程先改變變量的值 print('主進程 variable_id={}'.format(id(variable))) print('主進程 variable={}'.format(variable)) os.wait()
[root@192 ~]# python fork.py
子進程 variable_id=140426199897224
子進程 variable=[1]
主進程 variable_id=140426199897224
主進程 variable=[]
結論:子進程中改變了變量的值,但在父進程中並未改變,說明進程間全局變量不共享
問2:但為什么變量id一樣?
問2的解釋: 寫時復制技術:內核只為新生成的子進程創建虛擬空間結構,它們復制於父進程的虛擬空間結構,但是不為這些段分配物理內存,它們共享父進程的物理空間,當父子進程中有更改相應段的行為發生時,再為子進程相應的段分配物理空間。因此不論子進程有沒有修改操作,虛擬地址和父進程是一樣的,兩個進程查看變量的id值是相同的。
另外,即使是兩個互不相干的進程,若它們的邏輯地址相同,實際地址也是不同的,並不會產生沖突。
參考鏈接:
python的os模塊fork、wait、system、exec、popen、exit函數講解
如有意見或建議,一起交流;如有侵權,請告知刪除。