文章目錄
一、前言
需求是: 一個正在運行的腳本,當結束腳本的時候,需要獲取裏面的變量,如果變量值存在則執行插入數據操作。如果變量不存在則正常關閉腳本。
這個需求可以理解成是在要殺死腳本的時候,讓腳本監聽到這個終止事件,從而做一些事情,比如持久化數據之類的。在實現需求的時候碰到很多有意思的知識點,下面咱們就來講一下這些知識點。
二、最初的想法:直接獲取腳本中的變量值
剛開始是想着定義一個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模塊
通過不斷的百度,最終鎖定了python
的signal
模塊以及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模塊:
python
的atexit
模塊定義了一個register
函數,用於在python
解釋器中註冊一個退出函數,這個函數在解釋器正常終止時自動執行,一般用來做一些資源清理的操作。
但是這個函數,如果腳本是非正常crash
,或者通過os._exit()
退出,都不會調用退出函數。而且kill -2
和ctrl +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