Python守护进程和脚本单例运行
2019獨角獸企業(yè)重金招聘Python工程師標(biāo)準(zhǔn)>>>
一、簡介
? ???守護(hù)進(jìn)程最重要的特性是后臺運行;它必須與其運行前的環(huán)境隔離開來,這些環(huán)境包括未關(guān)閉的文件描述符、控制終端、會話和進(jìn)程組、工作目錄以及文件創(chuàng)建掩碼等;它可以在系統(tǒng)啟動時從啟動腳本/etc/rc.d中啟動,可以由inetd守護(hù)進(jìn)程啟動,也可以有作業(yè)規(guī)劃進(jìn)程crond啟動,還可以由用戶終端(通常是shell)執(zhí)行。
???????Python有時需要保證只運行一個腳本實例,以避免數(shù)據(jù)的沖突。?
二、Python守護(hù)進(jìn)程
1、函數(shù)實現(xiàn)
#!/usr/bin/env?python?? #coding:?utf-8?? import?sys,?os??'''將當(dāng)前進(jìn)程fork為一個守護(hù)進(jìn)程??注意:如果你的守護(hù)進(jìn)程是由inetd啟動的,不要這樣做!inetd完成了??所有需要做的事情,包括重定向標(biāo)準(zhǔn)文件描述符,需要做的事情只有chdir()和umask()了?? '''??def?daemonize?(stdin='/dev/null',?stdout='/dev/null',?stderr='/dev/null'):??#重定向標(biāo)準(zhǔn)文件描述符(默認(rèn)情況下定向到/dev/null)??try:???pid?=?os.fork()???#父進(jìn)程(會話組頭領(lǐng)進(jìn)程)退出,這意味著一個非會話組頭領(lǐng)進(jìn)程永遠(yuǎn)不能重新獲得控制終端。??if?pid?>?0:??sys.exit(0)???#父進(jìn)程退出??except?OSError,?e:???sys.stderr.write?("fork?#1?failed:?(%d)?%s\n"?%?(e.errno,?e.strerror)?)??sys.exit(1)??#從母體環(huán)境脫離??os.chdir("/")??#chdir確認(rèn)進(jìn)程不保持任何目錄于使用狀態(tài),否則不能umount一個文件系統(tǒng)。也可以改變到對于守護(hù)程序運行重要的文件所在目錄??os.umask(0)????#調(diào)用umask(0)以便擁有對于寫的任何東西的完全控制,因為有時不知道繼承了什么樣的umask。??os.setsid()????#setsid調(diào)用成功后,進(jìn)程成為新的會話組長和新的進(jìn)程組長,并與原來的登錄會話和進(jìn)程組脫離。??#執(zhí)行第二次fork??try:???pid?=?os.fork()???if?pid?>?0:??sys.exit(0)???#第二個父進(jìn)程退出??except?OSError,?e:???sys.stderr.write?("fork?#2?failed:?(%d)?%s\n"?%?(e.errno,?e.strerror)?)??sys.exit(1)??#進(jìn)程已經(jīng)是守護(hù)進(jìn)程了,重定向標(biāo)準(zhǔn)文件描述符??for?f?in?sys.stdout,?sys.stderr:?f.flush()??si?=?open(stdin,?'r')??so?=?open(stdout,?'a+')??se?=?open(stderr,?'a+',?0)??os.dup2(si.fileno(),?sys.stdin.fileno())????#dup2函數(shù)原子化關(guān)閉和復(fù)制文件描述符??os.dup2(so.fileno(),?sys.stdout.fileno())??os.dup2(se.fileno(),?sys.stderr.fileno())??#示例函數(shù):每秒打印一個數(shù)字和時間戳?? def?main():??import?time??sys.stdout.write('Daemon?started?with?pid?%d\n'?%?os.getpid())??sys.stdout.write('Daemon?stdout?output\n')??sys.stderr.write('Daemon?stderr?output\n')??c?=?0??while?True:??sys.stdout.write('%d:?%s\n'?%(c,?time.ctime()))??sys.stdout.flush()??c?=?c+1??time.sleep(1)??if?__name__?==?"__main__":??daemonize('/dev/null','/tmp/daemon_stdout.log','/tmp/daemon_error.log')??main()????????? 可以通過命令ps -ef | grep daemon.py查看后臺運行的繼承,在/tmp/daemon_error.log會記錄錯誤運行日志,在/tmp/daemon_stdout.log會記錄標(biāo)準(zhǔn)輸出日志。
2、類實現(xiàn)
#!/usr/bin/env?python?? #coding:?utf-8??#python模擬linux的守護(hù)進(jìn)程??import?sys,?os,?time,?atexit,?string?? from?signal?import?SIGTERM??class?Daemon:??def?__init__(self,?pidfile,?stdin='/dev/null',?stdout='/dev/null',?stderr='/dev/null'):??#需要獲取調(diào)試信息,改為stdin='/dev/stdin',?stdout='/dev/stdout',?stderr='/dev/stderr',以root身份運行。??self.stdin?=?stdin??self.stdout?=?stdout??self.stderr?=?stderr??self.pidfile?=?pidfile??def?_daemonize(self):??try:??pid?=?os.fork()????#第一次fork,生成子進(jìn)程,脫離父進(jìn)程??if?pid?>?0:??sys.exit(0)??????#退出主進(jìn)程??except?OSError,?e:??sys.stderr.write('fork?#1?failed:?%d?(%s)\n'?%?(e.errno,?e.strerror))??sys.exit(1)??os.chdir("/")??????#修改工作目錄??os.setsid()????????#設(shè)置新的會話連接??os.umask(0)????????#重新設(shè)置文件創(chuàng)建權(quán)限??try:??pid?=?os.fork()?#第二次fork,禁止進(jìn)程打開終端??if?pid?>?0:??sys.exit(0)??except?OSError,?e:??sys.stderr.write('fork?#2?failed:?%d?(%s)\n'?%?(e.errno,?e.strerror))??sys.exit(1)??#重定向文件描述符??sys.stdout.flush()??sys.stderr.flush()??si?=?file(self.stdin,?'r')??so?=?file(self.stdout,?'a+')??se?=?file(self.stderr,?'a+',?0)??os.dup2(si.fileno(),?sys.stdin.fileno())??os.dup2(so.fileno(),?sys.stdout.fileno())??os.dup2(se.fileno(),?sys.stderr.fileno())??#注冊退出函數(shù),根據(jù)文件pid判斷是否存在進(jìn)程??atexit.register(self.delpid)??pid?=?str(os.getpid())??file(self.pidfile,'w+').write('%s\n'?%?pid)??def?delpid(self):??os.remove(self.pidfile)??def?start(self):??#檢查pid文件是否存在以探測是否存在進(jìn)程??try:??pf?=?file(self.pidfile,'r')??pid?=?int(pf.read().strip())??pf.close()??except?IOError:??pid?=?None??if?pid:??message?=?'pidfile?%s?already?exist.?Daemon?already?running!\n'??sys.stderr.write(message?%?self.pidfile)??sys.exit(1)??#啟動監(jiān)控??self._daemonize()??self._run()??def?stop(self):??#從pid文件中獲取pid??try:??pf?=?file(self.pidfile,'r')??pid?=?int(pf.read().strip())??pf.close()??except?IOError:??pid?=?None??if?not?pid:???#重啟不報錯??message?=?'pidfile?%s?does?not?exist.?Daemon?not?running!\n'??sys.stderr.write(message?%?self.pidfile)??return??#殺進(jìn)程??try:??while?1:??os.kill(pid,?SIGTERM)??time.sleep(0.1)??#os.system('hadoop-daemon.sh?stop?datanode')??#os.system('hadoop-daemon.sh?stop?tasktracker')??#os.remove(self.pidfile)??except?OSError,?err:??err?=?str(err)??if?err.find('No?such?process')?>?0:??if?os.path.exists(self.pidfile):??os.remove(self.pidfile)??else:??print?str(err)??sys.exit(1)??def?restart(self):??self.stop()??self.start()??def?_run(self):??"""?run?your?fun"""??while?True:??#fp=open('/tmp/result','a+')??#fp.write('Hello?World\n')??sys.stdout.write('%s:hello?world\n'?%?(time.ctime(),))??sys.stdout.flush()???time.sleep(2)??if?__name__?==?'__main__':??daemon?=?Daemon('/tmp/watch_process.pid',?stdout?=?'/tmp/watch_stdout.log')??if?len(sys.argv)?==?2:??if?'start'?==?sys.argv[1]:??daemon.start()??elif?'stop'?==?sys.argv[1]:??daemon.stop()??elif?'restart'?==?sys.argv[1]:??daemon.restart()??else:??print?'unknown?command'??sys.exit(2)??sys.exit(0)??else:??print?'usage:?%s?start|stop|restart'?%?sys.argv[0]??sys.exit(2)??運行結(jié)果:
?????? 可以參考:http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/,它是當(dāng)Daemon設(shè)計成一個模板,在其他文件中from?daemon?import?Daemon,然后定義子類,重寫run()方法實現(xiàn)自己的功能。
class?MyDaemon(Daemon):??def?run(self):??while?True:??fp=open('/tmp/run.log','a+')??fp.write('Hello?World\n')??time.sleep(1)????????? 不足:信號處理signal.signal(signal.SIGTERM, cleanup_handler)暫時沒有安裝,注冊程序退出時的回調(diào)函數(shù)delpid()沒有被調(diào)用。
?????? 然后,再寫個shell命令,加入開機啟動服務(wù),每隔2秒檢測守護(hù)進(jìn)程是否啟動,若沒有啟動則啟動,自動監(jiān)控恢復(fù)程序。 ?????
三、python保證只能運行一個腳本實例
1、打開文件本身加鎖
#!/usr/bin/env?python?? #coding:?utf-8?? import?fcntl,?sys,?time,?os?? pidfile?=?0??def?ApplicationInstance():??global?pidfile??pidfile?=?open(os.path.realpath(__file__),?"r")??try:??fcntl.flock(pidfile,?fcntl.LOCK_EX?|?fcntl.LOCK_NB)?#創(chuàng)建一個排他鎖,并且所被鎖住其他進(jìn)程不會阻塞??except:??print?"another?instance?is?running..."??sys.exit(1)??if?__name__?==?"__main__":??ApplicationInstance()??while?True:??print?'running...'??time.sleep(1)??? ? ?? 注意:open()參數(shù)不能使用w,否則會覆蓋本身文件;pidfile必須聲明為全局變量,否則局部變量生命周期結(jié)束,文件描述符會因引用計數(shù)為0被系統(tǒng)回收(若整個函數(shù)寫在主函數(shù)中,則不需要定義成global)。? ? ? ?? ??????
2、打開自定義文件并加鎖
#!/usr/bin/env?python?? #coding:?utf-8?? import?fcntl,?sys,?time?? pidfile?=?0??def?ApplicationInstance():??global?pidfile??pidfile?=?open("instance.pid",?"w")??try:??fcntl.lockf(pidfile,?fcntl.LOCK_EX?|?fcntl.LOCK_NB)??#創(chuàng)建一個排他鎖,并且所被鎖住其他進(jìn)程不會阻塞??except??IOError:??print?"another?instance?is?running..."??sys.exit(0)??if?__name__?==?"__main__":??ApplicationInstance()??while?True:??print?'running...'??time.sleep(1)??3、檢測文件中PID
#!/usr/bin/env?python?? #coding:?utf-8?? import?time,?os,?sys?? import?signal??pidfile?=?'/tmp/process.pid'??def?sig_handler(sig,?frame):??if?os.path.exists(pidfile):??os.remove(pidfile)??sys.exit(0)??def?ApplicationInstance():??signal.signal(signal.SIGTERM,?sig_handler)??signal.signal(signal.SIGINT,?sig_handler)??signal.signal(signal.SIGQUIT,?sig_handler)??try:??pf?=?file(pidfile,?'r')??pid?=?int(pf.read().strip())??pf.close()??except?IOError:??pid?=?None??if?pid:??sys.stdout.write('instance?is?running...\n')??sys.exit(0)??file(pidfile,?'w+').write('%s\n'?%?os.getpid())??if?__name__?==?"__main__":??ApplicationInstance()??while?True:??print?'running...'??time.sleep(1)????
4、檢測特定文件夾或文件
#!/usr/bin/env?python?? #coding:?utf-8?? import?time,?commands,?signal,?sys??def?sig_handler(sig,?frame):??if?os.path.exists("/tmp/test"):??os.rmdir("/tmp/test")??sys.exit(0)??def?ApplicationInstance():??signal.signal(signal.SIGTERM,?sig_handler)??signal.signal(signal.SIGINT,?sig_handler)??signal.signal(signal.SIGQUIT,?sig_handler)??if?commands.getstatusoutput("mkdir?/tmp/test")[0]:??print?"instance?is?running..."??sys.exit(0)??if?__name__?==?"__main__":??ApplicationInstance()??while?True:??print?'running...'??time.sleep(1)???????? 也可以檢測某一個特定的文件,判斷文件是否存在:
import?os?? import?os.path?? import?time??#class?used?to?handle?one?application?instance?mechanism?? class?ApplicationInstance:??#specify?the?file?used?to?save?the?application?instance?pid??def?__init__(?self,?pid_file?):??self.pid_file?=?pid_file??self.check()??self.startApplication()??#check?if?the?current?application?is?already?running??def?check(?self?):??#check?if?the?pidfile?exists??if?not?os.path.isfile(?self.pid_file?):??return??#read?the?pid?from?the?file??pid?=?0??try:??file?=?open(?self.pid_file,?'rt'?)??data?=?file.read()??file.close()??pid?=?int(?data?)??except:??pass??#check?if?the?process?with?specified?by?pid?exists??if?0?==?pid:??return??try:??os.kill(?pid,?0?)???#this?will?raise?an?exception?if?the?pid?is?not?valid??except:??return??#exit?the?application??print?"The?application?is?already?running..."??exit(0)?#exit?raise?an?exception?so?don't?put?it?in?a?try/except?block??#called?when?the?single?instance?starts?to?save?it's?pid??def?startApplication(?self?):??file?=?open(?self.pid_file,?'wt'?)??file.write(?str(?os.getpid()?)?)??file.close()??#called?when?the?single?instance?exit?(?remove?pid?file?)??def?exitApplication(?self?):??try:??os.remove(?self.pid_file?)??except:??pass??if?__name__?==?'__main__':??#create?application?instance??appInstance?=?ApplicationInstance(?'/tmp/myapp.pid'?)??#do?something?here??print?"Start?MyApp"??time.sleep(5)???#sleep?5?seconds??print?"End?MyApp"??#remove?pid?file??appInstance.exitApplication()????????? 上述os.kill( pid, 0 )用于檢測一個為pid的進(jìn)程是否還活著,若該pid的進(jìn)程已經(jīng)停止則拋出異常,若正在運行則不發(fā)送kill信號。
5、socket監(jiān)聽一個特定端口
#!/usr/bin/env?python?? #coding:?utf-8?? import?socket,?time,?sys??def?ApplicationInstance():??try:??????global?s??s?=?socket.socket()??host?=?socket.gethostname()??s.bind((host,?60123))??except:??print?"instance?is?running..."??sys.exit(0)??if?__name__?==?"__main__":??ApplicationInstance()??while?True:??print?'running...'??time.sleep(1)??可以將該函數(shù)使用裝飾器實現(xiàn),便于重用(效果與上述相同):
?
四、總結(jié)
(1)守護(hù)進(jìn)程和單腳本運行在實際應(yīng)用中比較重要,方法也比較多,可選擇合適的來進(jìn)行修改,可以將它們做成一個單獨的類或模板,然后子類化實現(xiàn)自定義。
(2)daemon監(jiān)控進(jìn)程自動恢復(fù)避免了nohup和&的使用,并配合shell腳本可以省去很多不定時啟動掛掉服務(wù)器的麻煩。
(3)若有更好的設(shè)計和想法,可隨時留言,在此先感謝!
轉(zhuǎn)載于:https://my.oschina.net/mickelfeng/blog/968356
總結(jié)
以上是生活随笔為你收集整理的Python守护进程和脚本单例运行的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: [Leedcode][JAVA][第46
- 下一篇: 芒果文件服务器,芒果服务器