プログラムのインスタンスを 1 つだけ実行する Pythonic の方法はありますか?
私が思いついた唯一の合理的な解決策は、あるポートでサーバーとして実行しようとすることです。次に、同じポートにバインドしようとする2番目のプログラムが失敗します。しかし、それは本当に素晴らしいアイデアではありません。これよりも軽量なものがあるのではないでしょうか?
(プログラムが時々失敗することが予想されることを考慮してください。つまり、segfault-「ファイルのロック」などは機能しません)
プログラムのインスタンスを 1 つだけ実行する Pythonic の方法はありますか?
私が思いついた唯一の合理的な解決策は、あるポートでサーバーとして実行しようとすることです。次に、同じポートにバインドしようとする2番目のプログラムが失敗します。しかし、それは本当に素晴らしいアイデアではありません。これよりも軽量なものがあるのではないでしょうか?
(プログラムが時々失敗することが予想されることを考慮してください。つまり、segfault-「ファイルのロック」などは機能しません)
次のコードは、クロスプラットフォームであり、Python 2.4-3.2 で実行されます。Windows、OS X、および Linux でテストしました。
from tendo import singleton
me = singleton.SingleInstance() # will sys.exit(-1) if other instance is running
最新のコード バージョンはsingleton.pyで入手できます。ここにバグを報告してください。
次のいずれかの方法を使用して、tend をインストールできます。
easy_install tendo
pip install tendo
zgodaによる別の質問にあるシンプルなクロスプラットフォームソリューション:
import fcntl
import os
import sys
def instance_already_running(label="default"):
"""
Detect if an an instance with the label is already running, globally
at the operating system level.
Using `os.open` ensures that the file pointer won't be closed
by Python's garbage collector after the function's scope is exited.
The lock will be released when the program exits, or could be
released if the file pointer were closed.
"""
lock_file_pointer = os.open(f"/tmp/instance_{label}.lock", os.O_WRONLY)
try:
fcntl.lockf(lock_file_pointer, fcntl.LOCK_EX | fcntl.LOCK_NB)
already_running = False
except IOError:
already_running = True
return already_running
S.Lottの提案によく似ていますが、コードがあります。
このコードはLinux固有です。'abstract' UNIXドメインソケットを使用しますが、シンプルで、古いロックファイルを残しません。特別に予約されたTCPポートを必要としないため、上記のソリューションよりも優先します。
try:
import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
## Create an abstract socket, by prefixing it with null.
s.bind( '\0postconnect_gateway_notify_lock')
except socket.error as e:
error_code = e.args[0]
error_string = e.args[1]
print "Process already running (%d:%s ). Exiting" % ( error_code, error_string)
sys.exit (0)
一意の文字列postconnect_gateway_notify_lock
を変更して、単一のインスタンスを適用する必要がある複数のプログラムを許可することができます。
それが十分にpythonicかどうかはわかりませんが、Javaの世界では、定義されたポートでリッスンすることは、すべての主要なプラットフォームで機能し、プログラムをクラッシュさせる問題がないため、かなり広く使用されているソリューションです.
ポートをリッスンするもう 1 つの利点は、実行中のインスタンスにコマンドを送信できることです。たとえば、ユーザーがプログラムを 2 回目に起動したときに、実行中のインスタンスにコマンドを送信して、別のウィンドウを開くように指示することができます (たとえば、Firefox はこれを実行します。TCP ポートを使用するのか、名前付きパイプを使用するのか、またはそのようなもの、しかし)。
これまでに python を書いたことはありませんが、crond によって 2 回以上開始されるのを防ぐために、これを mycheckpoint に実装したばかりです。
import os
import sys
import fcntl
fh=0
def run_once():
global fh
fh=open(os.path.realpath(__file__),'r')
try:
fcntl.flock(fh,fcntl.LOCK_EX|fcntl.LOCK_NB)
except:
os._exit(0)
run_once()
これを別の問題 (http://stackoverflow.com/questions/2959474) に投稿した後、Slava-N の提案を見つけました。これは関数として呼び出され、実行中のスクリプト ファイル (pid ファイルではない) をロックし、スクリプトが終了するまで (通常またはエラーで) ロックを維持します。
pid ファイルを使用します。「/path/to/pidfile」という既知の場所があり、起動時に次のようなことを行います(私はコーヒーを飲む前で、それほど一生懸命働きたくないので、部分的に擬似コードです):
import os, os.path
pidfilePath = """/path/to/pidfile"""
if os.path.exists(pidfilePath):
pidfile = open(pidfilePath,"r")
pidString = pidfile.read()
if <pidString is equal to os.getpid()>:
# something is real weird
Sys.exit(BADCODE)
else:
<use ps or pidof to see if the process with pid pidString is still running>
if <process with pid == 'pidString' is still running>:
Sys.exit(ALREADAYRUNNING)
else:
# the previous server must have crashed
<log server had crashed>
<reopen pidfilePath for writing>
pidfile.write(os.getpid())
else:
<open pidfilePath for writing>
pidfile.write(os.getpid())
つまり、pidfile が存在するかどうかを確認しています。そうでない場合は、pid をそのファイルに書き込みます。pidfile が存在する場合は、pid が実行中のプロセスの pid であるかどうかを確認します。もしそうなら、別のライブプロセスが実行されているので、シャットダウンしてください。そうでない場合は、前のプロセスがクラッシュしたため、ログに記録してから、古いファイルの代わりに独自の pid をファイルに書き込みます。その後、続行します。
これはうまくいくかもしれません。
PID ファイルを既知の場所に作成してみます。あなたが失敗した場合、誰かがファイルをロックしています。
正常に終了したら、PID ファイルを閉じて削除し、他の人が上書きできるようにします。
プログラムがクラッシュした場合でも、PID ファイルを削除するシェル スクリプトでプログラムをラップできます。
また、PID ファイルを使用して、ハングした場合にプログラムを強制終了することもできます。
これが私の最終的なWindowsのみのソリューションです。おそらく「onlyone.py」などと呼ばれるモジュールに次のものを入れます。そのモジュールを __ main __ python スクリプト ファイルに直接インクルードします。
import win32event, win32api, winerror, time, sys, os
main_path = os.path.abspath(sys.modules['__main__'].__file__).replace("\\", "/")
first = True
while True:
mutex = win32event.CreateMutex(None, False, main_path + "_{<paste YOUR GUID HERE>}")
if win32api.GetLastError() == 0:
break
win32api.CloseHandle(mutex)
if first:
print "Another instance of %s running, please wait for completion" % main_path
first = False
time.sleep(1)
コードは、スクリプトへのフル パスから派生した名前でミューテックスを作成しようとします。実際のファイル システムとの混同を避けるために、スラッシュを使用しています。
ロックファイルの使用は、UNIX では非常に一般的なアプローチです。クラッシュした場合は、手動でクリーンアップする必要があります。PID をファイルに保存し、起動時にこの PID を持つプロセスがあるかどうかを確認し、そうでない場合はロックファイルを上書きすることができます。(ただし、read-file-check-pid-rewrite-file の周りにもロックが必要です)。osパッケージには、pid の取得とチェックに必要なものが含まれています。指定された pid を持つプロセスが存在するかどうかを確認する一般的な方法は、致命的ではないシグナルを送信することです。
他の代替手段として、これを flock または posix セマフォと組み合わせることができます。
saua が提案したように、ネットワーク ソケットを開くのがおそらく最も簡単で移植性が高いでしょう。
Roberto Rosario の回答に基づいて、次の関数を考え出しました。
SOCKET = None
def run_single_instance(uniq_name):
try:
import socket
global SOCKET
SOCKET = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
## Create an abstract socket, by prefixing it with null.
# this relies on a feature only in linux, when current process quits, the
# socket will be deleted.
SOCKET.bind('\0' + uniq_name)
return True
except socket.error as e:
return False
SOCKET
プロセス全体が終了したときにのみガベージ コレクションが行われるため、global vaiableを定義する必要があります。関数内でローカル変数を宣言すると、関数の終了後にスコープ外になるため、ソケットが削除されます。
私は彼のコードを明確にし、詳しく説明するだけなので、すべての功績は Roberto Rosario に帰すべきです。また、このコードは Linux でのみ機能します。https ://troydhanson.github.io/network/Unix_domain_sockets.html からの次の引用テキストで説明されています。
Linux には特別な機能があります。UNIX ドメイン ソケットのパス名がヌル バイト \0 で始まる場合、その名前はファイル システムにマップされません。したがって、ファイルシステム内の他の名前と衝突しません。また、サーバーが抽象名前空間の UNIX ドメイン リスニング ソケットを閉じると、そのファイルは削除されます。通常の UNIX ドメイン ソケットでは、サーバーがファイルを閉じた後もファイルが保持されます。
私は新しいユーザーであり、Stack Overflow ではまだ投票できないため、これを回答として投稿しています。
Sorin Sbarnea のソリューションは、OS X、Linux、および Windows で動作し、感謝しています。
ただし、tempfile.gettempdir() は、OS X と Windows では 1 つの方法で動作し、他の一部/多数/すべて (?) *nix では別の方法で動作します (OS X も Unix であるという事実を無視して!)。この違いは、このコードにとって重要です。
OS X と Windows にはユーザー固有の一時ディレクトリがあるため、あるユーザーが作成した一時ファイルは別のユーザーには表示されません。対照的に、*nix の多くのバージョン (Ubuntu 9、RHEL 5、OpenSolaris 2008、および FreeBSD 8 をテストしました) では、すべてのユーザーの一時ディレクトリは /tmp です。
つまり、ロックファイルがマルチユーザー マシンで作成されると、/tmp に作成され、最初にロックファイルを作成したユーザーだけがアプリケーションを実行できるようになります。
考えられる解決策は、現在のユーザー名をロック ファイルの名前に埋め込むことです。
ポートを取得するOPのソリューションも、マルチユーザーマシンで誤動作することに注意してください。
ファイルシステムにアクセスする必要のない、プロセスグループを使用した優れたPOSIXyソリューションがあるはずだと私はずっと思っていますが、それを突き止めることはできません。何かのようなもの:
起動時に、プロセスは特定のグループ内のすべてのプロセスに「kill -0」を送信します。そのようなプロセスが存在する場合は、終了します。その後、グループに参加します。他のプロセスはそのグループを使用しません。
ただし、これには競合状態があります。複数のプロセスがすべて同時にこれを実行し、すべてがグループに参加して同時に実行される可能性があります。ある種のミューテックスを追加して水密にするまでには、プロセスグループは必要なくなります。
これは、プロセスが毎分または毎時間 cron によってのみ開始される場合は許容されるかもしれませんが、実行したくない日に正確にエラーが発生するのではないかと少し不安になります。
誰かが改善できない限り、これは結局のところあまり良い解決策ではないと思いますか?
私は先週まさにこの問題に遭遇し、いくつかの良い解決策を見つけましたが、非常にシンプルでクリーンな python パッケージを作成して PyPI にアップロードすることにしました。任意の文字列リソース名をロックできるという点で、tendo とは異なります。確かにロック__file__
して同じ効果を得ることができますが。
インストール:pip install quicklock
使用方法は非常に簡単です。
[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep 9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> # Let's create a lock so that only one instance of a script will run
...
>>> singleton('hello world')
>>>
>>> # Let's try to do that again, this should fail
...
>>> singleton('hello world')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/Users/nate/live/gallery/env/lib/python2.7/site-packages/quicklock/quicklock.py", line 47, in singleton
raise RuntimeError('Resource <{}> is currently locked by <Process {}: "{}">'.format(resource, other_process.pid, other_process.name()))
RuntimeError: Resource <hello world> is currently locked by <Process 24801: "python">
>>>
>>> # But if we quit this process, we release the lock automatically
...
>>> ^D
[nate@Nates-MacBook-Pro-3 ~/live] python
Python 2.7.6 (default, Sep 9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> from quicklock import singleton
>>> singleton('hello world')
>>>
>>> # No exception was thrown, we own 'hello world'!
Linux の例
この方法は、アプリケーションを閉じた後に自動的に削除される一時ファイルの作成に基づいています。プログラムの起動時にファイルの存在を確認します。ファイルが存在する場合 (保留中の実行がある場合)、プログラムは閉じられます。それ以外の場合は、ファイルを作成し、プログラムの実行を続行します。
from tempfile import *
import time
import os
import sys
f = NamedTemporaryFile( prefix='lock01_', delete=True) if not [f for f in os.listdir('/tmp') if f.find('lock01_')!=-1] else sys.exit()
YOUR CODE COMES HERE