python腳本如何監聽終止進程行爲,如何通過腳本名獲取pid

一、前言

需求是: 一個正在運行的腳本,當結束腳本的時候,需要獲取裏面的變量,如果變量值存在則執行插入數據操作。如果變量不存在則正常關閉腳本。

      這個需求可以理解成是在要殺死腳本的時候,讓腳本監聽到這個終止事件,從而做一些事情,比如持久化數據之類的。在實現需求的時候碰到很多有意思的知識點,下面咱們就來講一下這些知識點。

二、最初的想法:直接獲取腳本中的變量值

      剛開始是想着定義一個test()方法,當要結束a腳本的時候,先執行另一個b腳本,調用a腳本的test()方法獲取變量,然後對比變量的值,符合條件就執行數據庫操作等。

1、獲取運行中py腳本的變量值

剛開始是想着獲取腳本中的變量,例如變量名是test,通過類似於:

import a 
print a.test

      但是這種方式只能獲取變量的初始值,比如在a腳本剛開始的時候,設置 test =1,後續代碼中累加test的值,我們通過這種方式,獲取的test的值一直都是1,並不能獲取腳本中變量的實時值

2、換一種方式

參考:https://blog.csdn.net/shiyf/article/details/47093899

      完全按照這個參考博客的代碼,本地測試的結果還是一樣的,並不能獲取腳本中的變量。仔細想想也是,人家腳本運行的時候,肯定是在一套封閉的環境中運行,想從中拿到變量不太科學。雖然是這麼想,但是並不知道其中的原理

3、爲什麼不能獲取腳本中的變量

main.py 文件:

import other

isRunning = True
def shutdown():
    global isRunning
    isRunning = False

def main():
    while isRunning:
        # some logic here
        other.check()

if __name__ == '__main__':
    main()

other.py 文件:

import main

def check():
    someCondition = #some logic to check the condition
    if someCondition:
        main.shutdown()

      這個例子非常相似,這位朋友的main.py腳本是根據isRunning 變量一直循環運行的腳本。他想通過other腳本改變isRunning 的值,從而中止main.py腳本的運行,和咱們的需求很相似了,但是他也一直實現不了自己的需求,後來有大佬是這麼解釋的:

      該問題與正在加載兩次的main.py文件有關.當您將其作爲腳本運行時,它首先作爲__main__加載.它也被other.py導入爲常規名稱main。這兩個副本彼此完全分開,並且具有單獨的全局變量!當other.check調用main.shutdown來修改isRunning全局變量時,它只更改main.isRunning版本,而不是__main __.isRunning.主要功能是檢查__main __.isRunning,它永遠不會被改變。

      這就能解釋爲什麼上面咱們通過引入的方式,每次都只能獲取變量的初始值,而不能獲取運行中腳本的變量。那麼是否真的毫無辦法呢?

4、下下策,使用使用python的gdb調試工具

參考:https://blog.csdn.net/xuzhina/article/details/76733977

      這塊大家可以自己百度下,知識點還是挺多的,只是使用gdb調試工具,相當於要自己手動執行一些查詢操作,而且過程比較複雜,並不建議使用這種方式。

三、python腳本監聽終止進程行爲

      既然不能獲取變量,那麼能不能自己監聽中止事件呢,當檢測到腳本要停止的時候,從而執行一系列的操作。而且這種方式的話,相當於是在a.py腳本內完成的操作,那麼直接使用腳本中的變量即可。

1、python的signal模塊以及atexit模塊

通過不斷的百度,最終鎖定了pythonsignal模塊以及atexit模塊。

signal模塊:

通過模塊的特定方法,可以監聽到腳本終止事件,從而執行相關操作,主要的方法包括:

 signal.SIGHUP  # 連接掛斷	(適用於nohub守護進程的關閉)
 signal.SIGILL  # 非法指令
 signal.SIGINT  # 連接中斷	(ctrl + c 和 kill -2)
 signal.SIGKILL # 終止進程(此信號不能被捕獲或忽略)
 signal.SIGQUIT # 終端退出
 signal.SIGTERM # 終止	(kill pid)
 signal.SIGALRM  # 超時警告
 signal.SIGCONT  # 繼續執行暫停進程

atexit模塊:

      pythonatexit模塊定義了一個register函數,用於在python解釋器中註冊一個退出函數,這個函數在解釋器正常終止時自動執行,一般用來做一些資源清理的操作。

      但是這個函數,如果腳本是非正常crash,或者通過os._exit()退出,都不會調用退出函數。而且kill -2ctrl +c這種退出也不能調用該函數,因此一般要與signal模塊配合使用。

注意: 這兩個模塊都不能監聽kill -9這種方式的終止腳本,因爲執行kill -9之後,內核根本不聽你多嗶嗶,直接就把進程強制刪除了。

參考:
https://www.jianshu.com/p/2fef52b7a231
https://www.return520.com/posts/9865/

2、使用os.kill()退出程序

      因爲要模擬退出程序,首選是kill的方式退出程序,這裏選擇os.kill()的方式退出,該函數是模擬傳統的UNIX函數發信號給進程,其中包含兩個參數:

一個是進程名,即所要接收信號的進程;一個是所要進行的操作。
操作(第二個參數)的常用取值爲:
SIGINT      終止進程     中斷進程
SIGTERM   終止進程     軟件終止信號
SIGKILL    終止進程      殺死進程
SIGALRM   鬧鐘信號

      結合上面咱們對於signal模塊的分析,選用signal.SIGTERM的方式是比較合適的,主要監聽的是kill pid的方式。因此中止代碼如下:

os.kill(PID, signal.SIGTERM)

3、腳本監聽中止信號代碼實例

a腳本:

import signal
import os
import time
def onsignal_term(a, b):
    print 'SIGTERM'
signal.signal(signal.SIGTERM, onsignal_term)
while 1:
    print 'id:', os.getpid()
    time.sleep(2)

b 腳本:

import getopt
import sys
PID = 1000
os.kill(PID, signal.SIGTERM)  # 注意此處的PID需要是int類型的
b腳本中的pid爲a腳本的pid,先執行a腳本,然後執行b腳本,此時觸發os.kill()函數,
a腳本中也會成功監聽到signal.SIGTERM時間,從而執行onsignal_term()函數。

注意: onsignal_term()函數需要有兩個參數,不過這兩個參數咱們也用不着,寫上就可以。

四、python通過腳本名獲取pid

      這一步更多的是完善上面的想法。通過pid殺死進程,a腳本成功監聽終止事件。只是這個pid不能每次都手動查詢把,這樣太費勁了,那麼能不能用過什麼方式來獲取到腳本的pid呢。

一種方案是通過傳參的方式,把pid傳進去,然後b腳本根據pid殺死進程;
一種方案是根據腳本名稱獲取pid,也是用傳參的方式,傳入腳本名稱;

      兩種方案都是通過傳參的方式,只是第一種方案的pid還需要手動查證或者腳本內打印pid,操作相對來說複雜點。第二種方案就簡單多了,只要知道腳本名稱即可,難點在於怎麼通過腳本名稱獲取pid。這裏選擇第二種方案。

1、通過腳本名稱獲取pid

File = '腳本名稱'
#直接寫死文件名
PID = os.popen("ps -ef | grep  a.py" ).readlines()[0].split()[1]  
#帶上變量的文件名 		
PID = os.popen("ps -ef | grep  %s" % File).readlines()[0].split()[1]	

      這裏採用腳本內調用linux命令行的方法來獲取,通過os.popen()獲取符合條件的進程,再調用read()或者readlines()等對shell的輸出結果進行讀取。

具體的可以參考:https://blog.51cto.com/2681882/2317053

2、腳本內獲取pid

順帶記錄一下吧,在腳本內獲取腳本pid的方法:

os.getpid()

3、單個腳本監聽命令行kill行爲

      以上都是雙腳本的監聽,那麼a腳本自己是否可以監聽命令行的操作呢,理論上肯定是可以的,那麼咱們也來試試

腳本a.py 如上所示:

執行a.py如下:

ljf@ljf:/var/www/python/Python2$ python2 a.py
id: 6340
id: 6340

按照腳本內容,會一直打印pid,直到捕獲到kill命令。

執行kill 6340,則腳本停止並打印SIGALRM:

id: 6340
id: 6340
SIGALRM

      其實單腳本和多腳本本質上是差不多的,我們在做多腳本的時候,順帶就也把單腳本的給做了,主要是看各自的業務吧。我這邊爲了給DBA方便,還是選擇多腳本,畢竟項目也多,直接輸入腳本名辨識度會高一些,而且也會更加安全。

如果要監聽ctrl+c或者kill -2 pid這種模式的中斷進程,那麼可以選用:signal.signal(signal.SIGINT, onsignal_term) 經過測試,這種方式也完全沒問題。當然,也可以選擇兩種方式都給寫上,這樣就能監聽:kill pid ,ctrl + c , kill -2 pid ,建議是能同時監聽多種情況,更方便一些。

身爲一個python小白,以上很多知識點都是值得好好總結的,奈何技術有限,希望以後有機會講一講“why”,而不是單純的“how”。

end

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章