以前面試,面試官問了一個問題,大意是:
我們在終端中,通過執行
python main.py
命令,會啟動一臺前臺程序直到程式結束。現在我還是想通過執行python main.py
,啟動一個後臺程序,讓後臺程序執行我們的業務邏輯。這個時候應該怎麼做呢?
回答上面這道題,需要先了解什麼是前臺程序和後臺程序,什麼是孤兒程序和殭屍程序?接下來,我們先一起看看前臺程序和後臺程序,以及孤兒程序和殭屍程序。最後再通過編寫程式碼來完成面試題的需求。
在 Linux 中,前臺程序是指當前正在執行的程序,它與使用者互動並佔用終端。當用戶在終端中輸入命令時,該命令所啟動的程序就是前臺程序。
前臺程序會佔用終端,直到它執行完畢或者被中斷(例如按下 Ctrl+C)。在前臺程序執行期間,使用者可以通過鍵盤輸入命令或者傳送訊號來與程序互動。
Linux 後臺程序是指在終端中執行的程序,但是不會佔用終端的輸入輸出,而是在後臺執行。
在 Linux 中,可以通過在命令後面加上 & 符號來將程序放到後臺執行。例如,執行命令 command &
就可以將 command
程序放到後臺執行。
後臺程序可以在終端關閉後繼續執行,也可以同時執行多個後臺程序。可以使用 jobs
命令檢視當前執行的後臺程序,使用 fg
命令將後臺程序切換到前臺執行,使用 bg
命令將前臺程序切換到後臺執行。
需要注意的是,後臺程序在另外的終端是檢視不到任何資訊的,如果需要檢視程序的輸出資訊,一般是將輸出重定向到檔案中,然後使用 tail
命令實時檢視輸出。
在 Linux 中,可以使用以下命令將前臺程序切換到後臺程序:
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
通過上面的講解,我們知道了什麼是後臺程序和前臺程序。接下來,我們再講講什麼是孤兒程序和殭屍程序。
孤兒程序是指父程序已經結束或者異常退出,而子程序還在執行的情況下,子程序就會變成孤兒程序
。孤兒程序會被作業系統的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程序成為該子程序的父程序了。
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檢視程序狀態。
注意:
這是每個子程序在結束時都要經過的階段。
那麼如何殺死殭屍程序呢,可以查詢 殭屍程序與孤兒程序 連結,來進行學習,這裡就不再講述。
現在再回過頭來看 面試題的要求:
在終端中執行
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
參考資料: