web-dev-qa-db-ja.com

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

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

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

(プログラムが時々失敗することが予想されることを考慮してください。つまり、segfault-「ロックファイル」のようなものは動作しません)

106
Slava V

次のコードは、クロスプラットフォームであり、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バグをここに登録してください

次のいずれかの方法を使用して、傾向をインストールできます。

88
sorin

シンプル、 クロスプラットフォーム 解決策、別の質問zgoda

import fcntl, sys
pid_file = 'program.pid'
fp = open(pid_file, 'w')
try:
    fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
    # another instance is running
    sys.exit(0)

S.Lottの提案によく似ていますが、コードがあります。

35
Slava V

このコードは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は、単一のインスタンスを強制する必要がある複数のプログラムを許可するように変更できます。

27
Roberto Rosario

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

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

22
Joachim Sauer

以前に書かれた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()

別の問題でこれを投稿した後、Slava-Nの提案を見つけました(http://stackoverflow.com/questions/2959474)。これは関数として呼び出され、実行中のスクリプトファイル(pidファイルではない)をロックし、スクリプトが終了するまで(正常またはエラー)ロックを維持します。

11
M.D. Klapwijk

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

10
Charlie Martin

あなたはすでに別のスレッドで同様の質問への回答を見つけたので、完全を期すために、名前付きミューテックスを解除するWindowsで同じことを達成する方法を参照してください。

http://code.activestate.com/recipes/474070/

6
zgoda

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

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

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

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

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

5
S.Lott

アプリケーションにwxPythonを使用している人は、関数_wx.SingleInstanceChecker_ ここに記載 を使用できます。

個人的に_wx.App_のサブクラスを使用します。これは_wx.SingleInstanceChecker_を利用し、OnInit()からFalseを返します。

_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.App_をSingleAppに置き換えるだけです:

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

これが私の最終的な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)

説明

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

長所

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

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

他の選択肢として、これをflockまたはposixセマフォと組み合わせることもできます。

Sauaが提案したように、ネットワークソケットを開くことは、おそらく最も簡単で最も移植性が高いでしょう。

3
Rolf Rander

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")

一部の回答では、Windowsでは利用できないfctnl(@sorin tendoパッケージにも含まれています)を使用しています。pyinstaller静的インポートを行うと、エラーがスローされます。

また、ロックファイルメソッドを使用して、read-onlyデータベースファイルの問題(sqlite3)。

2
Chuck G

私が使う single_process私のgentoo;

pip install single_process

example

from single_process import single_process

@single_process
def main():
    print 1

if __== "__main__":
    main()   

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

2
gkiwi

私は新しいユーザーであり、Stack Overflowではまだ投票できないため、これを回答として投稿しています。

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のポートを取得するソリューションは、マルチユーザーマシン上でも誤動作することに注意してください。

2

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
0
kerwal

先週この正確な問題に遭遇し、いくつかの良い解決策を見つけましたが、非常にシンプルできれいなpythonパッケージを作成してPyPIにアップロードすることにしました。任意の文字列リソース名をロックできますが、__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

0
Nate Ferrero

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__)))

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

0
user71769

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

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

ただし、これには競合状態があります。複数のプロセスがすべてこれをまったく同時に行うことができ、すべてがグループに参加して同時に実行されることになります。ある種のミューテックスを追加して水密にするまでには、プロセスグループは不要になります。

これは、プロセスが1分ごとまたは1時間ごとにcronで開始される場合に受け入れられるかもしれませんが、それを望まない日に正確にうまくいかないので少し不安になります。

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

0