1、面試題介紹
以前面試,面試官問了一個問題,大意是:
我們在終端中,透過執行
python main.py
命令,會啟動一臺前臺程式直到程式結束。現在我還是想透過執行python main.py
,啟動一個後臺程式,讓後臺程式執行我們的業務邏輯。這個時候應該怎麼做呢?
回答上面這道題,需要先了解什麼是前臺程式和後臺程式,什麼是孤兒程式和殭屍程式?接下來,我們先一起看看前臺程式和後臺程式,以及孤兒程式和殭屍程式。最後再透過編寫程式碼來完成面試題的需求。
2、前臺程式和後臺程式
2.1 什麼是前臺程式
在 Linux 中,前臺程式是指當前正在執行的程式,它與使用者互動並佔用終端。當使用者在終端中輸入命令時,該命令所啟動的程式就是前臺程式。
前臺程式會佔用終端,直到它執行完畢或者被中斷(例如按下 Ctrl+C)。在前臺程式執行期間,使用者可以透過鍵盤輸入命令或者傳送訊號來與程式互動。
2.2 什麼是後臺程式
Linux 後臺程式是指在終端中執行的程式,但是不會佔用終端的輸入輸出,而是在後臺執行。
在 Linux 中,可以透過在命令後面加上 & 符號來將程式放到後臺執行。例如,執行命令 command &
就可以將 command
程式放到後臺執行。
後臺程式可以在終端關閉後繼續執行,也可以同時執行多個後臺程式。可以使用 jobs
命令檢視當前執行的後臺程式,使用 fg
命令將後臺程式切換到前臺執行,使用 bg
命令將前臺程式切換到後臺執行。
需要注意的是,後臺程式在另外的終端是檢視不到任何資訊的,如果需要檢視程式的輸出資訊,一般是將輸出重定向到檔案中,然後使用 tail
命令實時檢視輸出。
2.3 前臺程式、後臺程式如何切換
在 Linux 中,可以使用以下命令將前臺程式切換到後臺程式:
- 使用 Ctrl + Z 將當前正在執行的前臺程式掛起。
- 使用
bg
命令將掛起的程式切換到後臺執行。
使用 command &
,這樣的方式是一開始就將 command
程式放到後臺執行。
我們透過下面這個例子,來感受下前臺程式和後臺程式是如何切換的。
1、編寫 test.py
檔案。
import os
import time
def main():
a = 1
while True:
time.sleep(1)
a += 1
print("a---->", a)
if a > 30:
break
if __name__ == '__main__':
main()
2、透過 python test.py
執行程式。然後使用ctrl + Z
是程式暫停到後臺。注意,這個時候程式不會再往下執行了。
-
如果想程式繼續往下執行,使用
bg
命令將其切換到後臺執行。 -
還有一種方式是:在最開始執行命令的時候,加上
&
。即python main.py &
。
python test.py
a----> 2
a----> 3
^Z
[1] + 1761 suspended python test.py
sample_test [master●] %
3、透過jobs
命令查詢後臺程式。
sample_test [master●] % jobs
[1] + suspended python test.py
sample_test [master●] %
4、使用fg
命令將指定的後臺程式切換到前臺執行。(fg %N
,這裡的N是 jobs
返回的序號,並不是程式的PID)
sample_test [master●] % fg %1
[1] + 1761 continued python test.py
a----> 4
a----> 5
a----> 6
a----> 7
3、孤兒程式和殭屍程式
透過上面的講解,我們知道了什麼是後臺程式和前臺程式。接下來,我們再講講什麼是孤兒程式和殭屍程式。
3.1 什麼是孤兒程式
孤兒程式是指父程式已經結束或者異常退出,而子程式還在執行的情況下,子程式就會變成孤兒程式
。孤兒程式會被作業系統的init程式接管
,init程式
會成為孤兒程式的新的父程式,並負責回收孤兒程式的資源,避免資源洩露和浪費。
因此一般來說,孤兒程式並不會有什麼危害。
我們來看一個關於孤兒程式的例子:
在main函式中,建立子程式,然後讓父程式睡眠1s,讓子程式先執行列印出其程式id(pid)以及父程式id(ppid);隨後子程式睡眠3s(此時會排程到父程式執行直至結束),目的是讓父程式先於子程式結束,讓子程式有個孤兒的狀態;最後子程式再列印出其程式id(pid)以及父程式id(ppid);觀察兩次列印 其父程式id(ppid)的區別。
import os
import time
def main():
pid = os.fork() # 建立子程式
if pid < 0:
# 建立失敗
print("fork failed!")
exit(1)
elif pid == 0: # 子程式
print("I am the child process.")
print("pid:%d, ppid:%d " % (os.getpid(), os.getppid()))
# 子程式睡眠3s,保證父程式先退出,此後子程式成為孤兒程式
time.sleep(3)
# 注意檢視孤兒程式的父程式變化
print("after sleep, pid:%d, ppid:%d" % (os.getpid(), os.getppid()))
assert os.getppid() == 1
print("child process is exited.")
else:
print("I am father process.")
# 為保證子程式先執行,讓父程式睡眠1s
time.sleep(1)
print("father process is exited.")
if __name__ == '__main__':
main()
執行結果:
執行結果表明:當父程式結束後,子程式成為了孤兒程式。因為它的父程式id(ppid)變成了1,即init程式成為該子程式的父程式了。
3.2 什麼是殭屍程式
Linux 殭屍程式是指已經結束執行的程式,但是其父程式還沒有對其進行處理,導致該程式的程式描述符仍然存在於系統中
,這種程式被稱為殭屍程式。
殭屍程式不會佔用系統資源,但是如果大量的殭屍程式存在,會佔用系統的程式描述符資源,導致系統程式描述符資源耗盡,從而導致系統崩潰。
為了避免殭屍程式的出現,父程式需要及時對其進行處理,可以使用 wait() 或 waitpid()
等系統呼叫來等待子程式結束並回收其資源。另外,也可以使用訊號處理機制,在父程式中註冊 SIGCHLD 訊號處理函式來處理子程式結束的訊號。
關於殭屍程式的例子:
在main函式中,建立子程式,然後讓父程式睡眠30s,讓子程式先終止(注意和孤兒程式例子的區別);這裡子程式結束後父程式沒有呼叫wait/waitpid函式獲取其狀態,用ps檢視程式狀態可以看出子程式為殭屍狀態。
import os
import time
def main():
pid = os.fork() # 建立子程式
if pid < 0:
# 建立失敗
print("fork failed!")
exit(1)
elif pid == 0: # 子程式
print("I am the child process.I am exited.")
exit(0)
else:
print("I am father process.")
# 父程式睡眠30s等待子程式退出,且沒有呼叫wait/waitpid獲取其狀態
# 子程式會成為殭屍程式
time.sleep(30)
print("father process is exited.")
if __name__ == '__main__':
main()
開一個終端,在終端是執行test.py
。
在子程式結束,父程式睡眠(還沒退出)的時候,再開一個終端用PS檢視程式狀態。
注意:
- 任何一個子程式(init除外)在exit()之後,其實它並沒有真正的被銷燬,而是留下一個稱為殭屍程式(Zombie)的資料結構,等待父程式處理。
這是每個子程式在結束時都要經過的階段。
- 如果子程式在exit()之後,父程式沒有來得及處理,這時用ps命令就能看到子程式的狀態是“Z”。如果父程式能及時處理,可能用ps命令就來不及看到子程式的殭屍狀態,但這並不等於子程式不經過殭屍狀態。
- 如果父程式在子程式結束之前退出,則子程式將由init接管。init將會以父程式的身份對殭屍狀態的子程式進行處理。
- 如果父程式是一個迴圈,不會結束,那麼子程式就會一直保持殭屍狀態,這就是系統中有時候會有很多殭屍程式的原因。
那麼如何殺死殭屍程式呢,可以查詢 殭屍程式與孤兒程式 連結,來進行學習,這裡就不再講述。
3.3 總結
- 孤兒程式:父程式已亡,子程式成為孤兒,被init程式收養,由init程式對它們完成狀態收集工作,因此一般沒有壞的影響。
- 殭屍程式:子程式已亡,父程式沒給子程式收屍,子程式成為殭屍程式,佔用系統資源。
4、面試題解決方式
現在再回過頭來看 面試題的要求:
在終端中執行
python main.py
命令,啟動後臺程式來進行業務處理。
那麼我們可以利用孤兒程式的特性,完成上面的需求。
1、透過 os.fork()
建立子程式。
2、建立完成後,讓父程式退出,子程式繼續執行。
簡單案例:
import os
import time
def main():
pid = os.fork() # 建立子程式
a = 0
if pid < 0:
# 建立失敗
print("fork failed!")
exit(1)
elif pid == 0: # 子程式
for i in range(10):
time.sleep(1)
a += i
print("child process calculate result: %d" % a)
else:
print("I am father process.")
exit(0)
if __name__ == '__main__':
main()
sample_test [master●] % python test.py
I am father process.
10秒鐘過後
sample_test [master●] % child process calculate result: 45
參考資料: