プログラムの単一のインスタンスのみが実行されていることを確認してください


120

プログラムのインスタンスを1つだけ実行するPython的な方法はありますか?

私が思いついた唯一の合理的な解決策は、あるポートでサーバーとして実行しようとすることです。その後、同じポートにバインドしようとする2番目のプログラムが失敗します。しかし、それは本当に素晴らしいアイデアではありません、おそらくこれよりも軽量なものがあるのでしょうか?

(プログラムが時々失敗することが予想されること、すなわちsegfaultを考慮に入れてください-したがって、「ロックファイル」のようなものが機能しません)


1
segfaultを追跡して修正すると、おそらくあなたの人生はより簡単になるでしょう。簡単なことではありません。
David Locke

それは私のライブラリにはありません、それはpythonのlibxmlバインディングにあり、非常に恥ずかしがり屋です-数日1回だけ起動します。
Slava V

5
Pythonの標準ライブラリは、flock()をサポートしています。これは、最新のUNIXプログラムにとって正しいことです。ポートを開くと、はるかに制約された名前空間のスポットが使用されますが、pidfileは、実行中のプロセスをチェックして安全に無効化する必要があるため、より複雑です。群れはどちらの問題もありません。
Charles Duffy

s / UNIX / linux /いよいよFTFY。
kaleissin 14

回答:


100

次のコードはその仕事をするはずです、それはクロスプラットフォームであり、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をインストールできます。


2
回答を更新し、最新バージョンへのリンクを含めました。バグを見つけた場合は、githubに送信してください。できるだけ早く解決します。
ソリン、

2
@Johny_Mのおかげで、私は、パッチを作り、上の新しいバージョンのリリースpypi.python.org/pypi/tendo
ソリン

2
この構文は、Python 2.6のWindowsでは機能しませんでした。私のために働いたのは、1:from teno import singleton 2:me = singleton.SingleInstance()
Brian

25
これと同じくらい些細なことに対する別の依存関係?あまり魅力的に聞こえません。
WhyNotHugo 2012

2
シングルトンはsigtermを取得するプロセスを処理しますか(たとえば、プロセスの実行時間が長すぎる場合)、またはそれを処理する必要がありますか?
JimJty 2014

43

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の提案によく似ていますが、コードが含まれています。


好奇心から:これは本当にクロスプラットフォームですか?Windowsで動作しますか?
ヨアヒムザウアー

1
fcntlWindowsにはモジュールがありません(機能はエミュレートできます)。
jfs 2008

10
ヒント:これを関数でラップする場合、「fp」はグローバルである必要があります。そうしないと、関数の終了後にファイルが閉じられます。
cmcginty

1
@Mirko Control + Zは(私が知っているどのOSでも)アプリケーションを終了せず、一時停止します。でアプリケーションをフォアグラウンドに戻すことができますfg。つまり、正常に機能しているように見えます(つまり、アプリはまだアクティブですが、一時停止されているため、ロックは維持されます)。
Sam Bull

1
私の状況でのこのコード(LinuxのPython 3.8.3)は変更が必要でした:lock_file_pointer = os.open(lock_path, os.O_WRONLY | os.O_CREAT)
baziorek

30

このコードはLinux固有です。「抽象的な」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を変更して、単一のインスタンスを適用する必要がある複数のプログラムを許可できます。


1
Roberto、カーネルパニックまたはハードリセットの後、ファイル\ 0postconnect_gateway_notify_lockが起動時に存在しないことを確認しますか?私の場合、AF_UNIXソケットファイルがこの後も存在し、これによりアイデア全体が破壊されます。この場合、特定のファイル名のロックを取得する上記の解決策は非常に信頼できます。
Daniel Gurianov 2013年

2
上述したように、このソリューションは、Linux上で動作しますが、ないのMac OS X上で
ビラルとオルガ

2
このソリューションは機能しません。Ubuntu 14.04で試してみました。2つのターミナルウィンドウから同じスクリプトを同時に実行します。どちらも問題なく動作します。
Dimon

1
これは私にとってUbuntu 16で機能しました。プロセスを強制終了すると、別のプロセスを開始できます。Dimon君はテストで何か間違ったことをしたと思う。(おそらく、上記のコードの実行後にスクリプトをスリープさせるのを忘れたため、すぐに終了してソケットを解放しました。)
Luke

1
睡眠の問題ではありません。コードは機能しますが、インラインコードとしてのみ機能します。関数に入れていました。関数が存在するや否や、ソケットは消えていました。
スティーブコーエン

25

Pythonで十分かどうかはわかりませんが、Javaの世界では、定義されたポートでリッスンすることは、非常に広く使用されているソリューションです。これは、すべての主要なプラットフォームで機能し、プログラムのクラッシュに問題がないためです。

ポートをリッスンするもう1つの利点は、実行中のインスタンスにコマンドを送信できることです。たとえば、ユーザーがもう一度プログラムを起動したときに、実行中のインスタンスにコマンドを送信して、別のウィンドウを開くように指示することができます(たとえば、Firefoxが実行していることです。TCPポートを使用しているか、名前付きパイプを使用しているか、またはそのようなものです」


それはなど、ので、それは別のウィンドウを作成し、私は実行中のインスタンスに通知することができますポップアップし、特別ので、これに+1、
WhyNotHugo

1
たとえばを使用しimport socket; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.bind(('localhost', DEFINED_PORT))ます。 OSError別のプロセスが同じポートにバインドされている場合に発生します。
crishoj 2018年

12

これまでに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ファイルではなく)をロックし、スクリプトが終了する(通常またはエラー)までロックを維持します。


とてもエレガント。スクリプトの引数からパスを取得するように変更しました。また、これを一般的などこかに埋め込むことをお勧めします-
Jossef Harush '27 / 07/13

10

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をファイルに書き込みます。その後続行します。


4
これには競合状態があります。test-then-writeシーケンスでは、2つのプログラムがほぼ同時に起動し、ファイルが見つからず、同時に書き込み用に開こうとすると、例外が発生する場合があります。それはすべき他の続行を許可する、1に例外を発生させます。
S.Lott、2008


5

これはうまくいくかもしれません。

  1. 既知の場所にPIDファイルを作成してみます。失敗した場合、誰かがファイルをロックしているので、完了です。

  2. 正常に終了したら、PIDファイルを閉じて削除し、他の誰かが上書きできるようにします。

プログラムがクラッシュした場合でもPIDファイルを削除するシェルスクリプトでプログラムをラップできます。

また、プログラムがハングした場合は、PIDファイルを使用してプログラムを強制終了できます。


3

ロックファイルを使用することは、UNIXでは非常に一般的なアプローチです。クラッシュした場合は、手動でクリーンアップする必要があります。ファイルにPIDを保存し、起動時にこのPIDを持つプロセスがあるかどうかを確認し、ない場合はロックファイルを上書きすることができます。(ただし、read-file-check-pid-rewrite-fileの周りにもロックが必要です)。os -packageでpidの取得とチェックに必要なものが見つかります。特定のpidを持つプロセスが存在するかどうかを確認する一般的な方法は、致命的でないシグナルを送信することです。

他の代替案は、これをflockまたはposixセマフォと組み合わせることです。

sauaが提案したように、ネットワークソケットを開くことは、おそらく最も簡単で最もポータブルです。


3

使用して誰のためのwxPythonを自分のアプリケーションのために、あなたは、関数を使用することができ wx.SingleInstanceChecker 、ここで説明します

私は個人的に、そのように実行されているアプリの既存のインスタンスがある場合にwx.Appそれを利用し、そこからwx.SingleInstanceChecker戻るサブクラスを使用します。FalseOnInit()

import wx

class SingleApp(wx.App):
    """
    class that extends wx.App and only permits a single running instance.
    """

    def OnInit(self):
        """
        wx.App init function that returns False if the app is already running.
        """
        self.name = "SingleApp-%s".format(wx.GetUserId())
        self.instance = wx.SingleInstanceChecker(self.name)
        if self.instance.IsAnotherRunning():
            wx.MessageBox(
                "An instance of the application is already running", 
                "Error", 
                 wx.OK | wx.ICON_WARNING
            )
            return False
        return True

これは、wx.App複数のインスタンスを禁止するための単純なドロップイン置換です。それを使用するにwx.AppSingleApp、次のようにコードで単に置き換えます:

app = SingleApp(redirect=False)
frame = wx.Frame(None, wx.ID_ANY, "Hello World")
frame.Show(True)
app.MainLoop()

シングルトンのソケットリストスレッドをコーディングした後、これが見つかりました。これはうまく機能し、すでにいくつかのプログラムにインストール済みですが、シングルトンに追加の「ウェイクアップ」を提供して、それを重なり合った窓の大きな山の正面中央。また、「ここに文書化」リンクは、かなり役に立たない自動生成の文書を指します。これは、より良いリンクです
RufusVS

@RufusVSそのとおりです-これははるかに優れたドキュメントリンクです。答えを更新しました。
Matt Coubrough 2016

3

これが私の最終的なWindowsのみのソリューションです。以下をおそらく 'onlyone.py'などと呼ばれるモジュールに入れてください。そのモジュールを__メイン__ 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)

説明

コードは、スクリプトへの完全パスから派生した名前でミューテックスを作成しようとします。実際のファイルシステムとの混乱を避けるために、スラッシュを使用します。

メリット

  • 設定や「マジック」識別子は必要ありません。必要なだけ多くの異なるスクリプトで使用してください。
  • 古くなったファイルは残っていません。ミューテックスはあなたと共に死にます。
  • 待機中に役立つメッセージを出力します

3

Windowsでこれを行うための最善の解決策は、@ zgodaの提案に従ってミューテックスを使用することです。

import win32event
import win32api
from winerror import ERROR_ALREADY_EXISTS

mutex = win32event.CreateMutex(None, False, 'name')
last_error = win32api.GetLastError()

if last_error == ERROR_ALREADY_EXISTS:
   print("App instance already running")

いくつかの答えは fctnlでは、Windowsでは使用できない(@sorin tendoパッケージにも含まれています)を使用しており、pyinstaller静的インポートを行うようなパッケージを使用してpythonアプリを凍結しようとすると、エラーがスローされます。

また、ファイルロックメソッドを使用するread-onlyと、データベースファイルに問題が発生します(でこれが発生しますsqlite3)。


2

私は新しいユーザーで、スタックオーバーフローではまだ投票できないので、これを回答として投稿します。

Sorin Sbarneaのソリューションは、OS X、Linux、およびWindowsで機能し、感謝しています。

ただし、tempfile.gettempdir()は、OS XとWindowsでは1つの方法で動作し、別のsome / many / all(?)* nixesでは動作します(OS XもUnixであるという事実を無視して!)。違いはこのコードにとって重要です。

OS XとWindowsにはユーザー固有の一時ディレクトリがあるため、あるユーザーが作成した一時ファイルは別のユーザーには表示されません。対照的に、* nixの多くのバージョン(私はUbuntu 9、RHEL 5、OpenSolaris 2008およびFreeBSD 8をテストしました)では、すべてのユーザーの一時ディレクトリは/ tmpです。

つまり、ロックファイルがマルチユーザーマシンで作成されると、/ tmpに作成され、最初にロックファイルを作成したユーザーだけがアプリケーションを実行できるようになります。

考えられる解決策は、ロックファイルの名前に現在のユーザー名を埋め込むことです。

ポートを取得するというOPのソリューションは、マルチユーザーマシンでも正しく動作しないことに注意してください。


一部の読者(たとえば、私)にとっては、関与するユーザーの数に関係なく、1つのコピーのみが期間を実行できることが望ましい動作です。そのため、ユーザーごとのtmpディレクトリは壊れていますが、共有/ tmpまたはポートロックは望ましい動作を示します。
ジョナサンハートレイ

2

single_processはジェンツーで使用しています。

pip install single_process

from single_process import single_process

@single_process
def main():
    print 1

if __name__ == "__main__":
    main()   

参照:https : //pypi.python.org/pypi/single_process/1.0


Py3で失敗します。パッケージが正しく構成されていないようです。
Ekevoo 2015

Windowsの場合:ImportError:fcntlという名前のモジュールはありません
Andrew W. Phillips

1

ファイルシステムにアクセスすることなく、プロセスグループを使用する優れたPOSIXyソリューションがあるはずだと私は考え続けていますが、それを完全に特定することはできません。何かのようなもの:

起動時に、プロセスは特定のグループのすべてのプロセスに「kill -0」を送信します。そのようなプロセスが存在する場合は、終了します。その後、グループに参加します。他のプロセスはそのグループを使用しません。

ただし、これには競合状態があります。複数のプロセスがすべて同時にこれを実行し、最終的にすべてがグループに参加して同時に実行される可能性があります。何らかのmutexを追加して水密にするまでに、プロセスグループは不要になります。

これは、プロセスが1分ごとまたは1時間ごとに1回だけcronによって開始される場合は許容できるかもしれませんが、望まない日に正確に失敗するのではないかと少し不安になります。

誰かがそれを改善することができない限り、これは結局非常に良い解決策ではないと思いますか?


1

私は先週この正確な問題に遭遇し、いくつかの良い解決策を見つけましたが、非常にシンプルでクリーンな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'!

見てください:https : //pypi.python.org/pypi/quicklock


1
「pip install quicklock」でインストールしたのですが、「from quicklock import singleton」で使用しようとすると、「ImportError:import import name 'singleton'」という例外が発生します。これはMacです。
grayaii 2016

クイックロックがpython 3では機能しないことがわかりました。それが私が失敗した理由です。
grayaii 2016

はい、申し訳ありませんが、将来の保証はありませんでした。それを機能させるための寄付を歓迎します!
Nate Ferrero

1

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変数は、プロセス全体が終了したときにのみガベージコレクションされるため、。関数でローカル変数を宣言すると、関数の終了後にスコープから外れるため、ソケットが削除されます。

私は彼のコードを明確にし、詳しく説明するだけなので、すべてのクレジットはRoberto Rosarioに送られるべきです。そして、このコードは、https//troydhanson.github.io/network/Unix_domain_sockets.htmlからの以下の引用テキストが説明するように、Linuxでのみ機能します。

Linuxには特別な機能があります。UNIXドメインソケットのパス名がnullバイト\ 0で始まる場合、その名前はファイルシステムにマッピングされません。したがって、ファイルシステム内の他の名前と衝突することはありません。また、サーバーが抽象名前空間のUNIXドメインリスニングソケットを閉じると、そのファイルは削除されます。通常のUNIXドメインソケットでは、サーバーが閉じた後もファイルは保持されます。


0

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

1
Stack Overflowへようこそ!この答えは正しいかもしれませんが、説明を追加してください。基になるロジックを提供することは、コードを提供することよりも重要です。これは、OPや他のリーダーがこの問題や同様の問題を修正するのに役立つためです。
CodeMouse92 2015年

これはスレッドセーフですか?チェックと一時ファイルの作成はアトミックではないようです...
coppit

0

Linuxシステムpgrep -aでは、インスタンスの数を尋ねることもでき ます。スクリプトはプロセスリストにあります(オプション-aにより、完全なコマンドライン文字列が表示されます)。例えば

import os
import sys
import subprocess

procOut = subprocess.check_output( "/bin/pgrep -u $UID -a python", shell=True, 
                                   executable="/bin/bash", universal_newlines=True)

if procOut.count( os.path.basename(__file__)) > 1 :        
    sys.exit( ("found another instance of >{}<, quitting."
              ).format( os.path.basename(__file__)))

-u $UID制限をすべてのユーザーに適用する必要がある場合は削除ます。免責事項:a)スクリプトの(ベース)名は一意であると想定されています。b)競合状態がある可能性があります。


-1
import sys,os

# start program
try:  # (1)
    os.unlink('lock')  # (2)
    fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (3)  
except: 
    try: fd=os.open("lock", os.O_CREAT|os.O_EXCL) # (4) 
    except:  
        print "Another Program running !.."  # (5)
        sys.exit()  

# your program  ...
# ...

# exit program
try: os.close(fd)  # (6)
except: pass
try: os.unlink('lock')  
except: pass
sys.exit()  

2
Stack Overflowへようこそ!このコードブロックで質問に答えることができますが、なぜそうなるのかについて少し説明を提供できれば最適です。回答を編集して、そのような説明を含めてください。
Artjom B.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.