ホームページ >バックエンド開発 >Python チュートリアル >Pythonデーモンとスクリプトシングルトンの動作の詳細説明
この記事は主に Python デーモンとスクリプトのシングルトン操作を紹介しています。編集者が非常に優れていると考えたので、参考として共有します。エディターで見てみましょう
1. はじめに
デーモン プロセスの最も重要な機能は、実行前に環境から隔離されている必要があります。これには、閉じられていないファイル記述子と制御端末、セッションが含まれます。プロセス グループ、作業ディレクトリ、ファイル作成マスクなど。システムの起動時に起動スクリプト /etc/rc.d から開始することも、inetd デーモンによって開始することも、ジョブ プランニング プロセスによって開始することもできます。 crond、またはユーザー端末 (通常はシェル) によって実行できます。
Python では、データの競合を避けるために、スクリプト インスタンスが 1 つだけ実行されていることを確認する必要がある場合があります。
2. Python デーモン
1. 関数の実装
#!/usr/bin/env python #coding: utf-8 import sys, os '''将当前进程fork为一个守护进程 注意:如果你的守护进程是由inetd启动的,不要这样做!inetd完成了 所有需要做的事情,包括重定向标准文件描述符,需要做的事情只有chdir()和umask()了 ''' def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): #重定向标准文件描述符(默认情况下定向到/dev/null) try: pid = os.fork() #父进程(会话组头领进程)退出,这意味着一个非会话组头领进程永远不能重新获得控制终端。 if pid > 0: sys.exit(0) #父进程退出 except OSError, e: sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) ) sys.exit(1) #从母体环境脱离 os.chdir("/") #chdir确认进程不保持任何目录于使用状态,否则不能umount一个文件系统。也可以改变到对于守护程序运行重要的文件所在目录 os.umask(0) #调用umask(0)以便拥有对于写的任何东西的完全控制,因为有时不知道继承了什么样的umask。 os.setsid() #setsid调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离。 #执行第二次fork try: pid = os.fork() 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) #进程已经是守护进程了,重定向标准文件描述符 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函数原子化关闭和复制文件描述符 os.dup2(so.fileno(), sys.stdout.fileno()) os.dup2(se.fileno(), sys.stderr.fileno()) #示例函数:每秒打印一个数字和时间戳 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_stdout.log に標準出力ログが記録されます。
2. クラス実装
#!/usr/bin/env python #coding: utf-8 #python模拟linux的守护进程 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'): #需要获取调试信息,改为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,生成子进程,脱离父进程 if pid > 0: sys.exit(0) #退出主进程 except OSError, e: sys.stderr.write('fork #1 failed: %d (%s)\n' % (e.errno, e.strerror)) sys.exit(1) os.chdir("/") #修改工作目录 os.setsid() #设置新的会话连接 os.umask(0) #重新设置文件创建权限 try: pid = os.fork() #第二次fork,禁止进程打开终端 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()) #注册退出函数,根据文件pid判断是否存在进程 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文件是否存在以探测是否存在进程 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) #启动监控 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 #杀进程 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)
実行結果:
デーモンをテンプレートとして設計し、他のファイルのデーモンからインポートし、サブクラスを定義して、 run() メソッドを書き換えた場合です。独自の機能を実現します。
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) がまだインストールされておらず、登録プログラム終了時のコールバック関数 delpid() が呼び出されていません。
次に、シェルコマンドを作成し、スタートアップサービスを追加し、2秒ごとにデーモンが起動しているかどうかを確認し、起動していない場合は起動し、回復プロセスを自動的に監視します。
#/bin/sh while true do count=`ps -ef | grep "daemonclass.py" | grep -v "grep"` if [ "$?" != "0" ]; then daemonclass.py start fi sleep 2 done
3. Python は、実行できるスクリプト インスタンスが 1 つだけであることを保証します
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) #创建一个排他锁,并且所被锁住其他进程不会阻塞 except: print "another instance is running..." sys.exit(1) if __name__ == "__main__": ApplicationInstance() while True: print 'running...' time.sleep(1)
注: open() パラメーターは w を使用できません。ファイル自体は上書きされます。pidfile はグローバル変数として宣言する必要があります。そうしないと、参照カウントが 0 になるため、ローカル変数のライフサイクルが終了し、ファイル記述子がシステムによってリサイクルされます (関数全体がmain 関数をグローバルとして定義する必要はありません)。
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) #创建一个排他锁,并且所被锁住其他进程不会阻塞 except IOError: print "another instance is running..." sys.exit(0) if __name__ == "__main__": ApplicationInstance() while True: print 'running...' time.sleep(1)
3. ファイル内のPIDを検出します
4.特定のフォルダーまたはファイルを検出します
#!/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)特定のファイルを検出して、そのファイルが存在するかどうかを確認することもできます。
#!/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)上記の os.kill(pid, 0) は、pid を持つプロセスがまだ生きているかどうかを検出するために使用されます。 pid プロセスが停止している場合、例外がスローされます。プロセスが実行中の場合、kill シグナルは送信されません。
5. ソケットは特定のポートをリッスンします
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()この関数は、簡単に再利用できるようにデコレーターを使用して実装できます (効果は上記と同じです)。
(1 ) 実際のアプリケーションでは、デーモン プロセスと単一スクリプトの操作がより重要であり、適切なメソッドを選択して変更し、それらを別のクラスまたはテンプレートにサブクラス化することができます。カスタマイズを実装します。
(2) デーモン監視プロセスの自動回復により、nohup と & の使用が回避され、シェル スクリプトと組み合わせることで、サーバーを時々起動したりハングしたりする多くの手間を省くことができます。以上がこの記事の全内容です。皆さんの学習に役立つことを願っています。また、皆さんも PHP 中国語 Web サイトをサポートしていただければ幸いです。