2010-05-29 49 views
10

我想阻止同一個長時間運行的python命令行腳本的多個實例同時運行,並且我希望新實例能夠在新實例之前將數據發送到原始實例自殺。我怎樣才能以跨平臺的方式做到這一點?python腳本可以知道同一腳本的另一個實例正在運行...然後與它交談?

具體來說,我想啓用以下行爲:

  1. foo.py」從命令行啓動,它會停留很長的時間 - 天或數週運行,直到機器重新啓動或父進程殺死它。
  2. 每隔幾分鐘再次啓動相同的腳本,但使用不同的命令行參數
  3. 啓動時,腳本應該看看是否有其他實例正在運行。
  4. 如果其他實例正在運行,則實例#2應將其命令行參數發送到實例#1,然後實例#2應該退出。
  5. 實例#1,如果它從另一個腳本接收到命令行參數,則應該啓動一個新線程並(使用上述步驟中發送的命令行參數)開始執行實例#2要執行的工作。

所以我在尋找兩件事情:一個python程序如何知道自己的另一個實例正在運行,然後一個python命令行程序如何與另一個進行通信?

使這個更復雜,相同的腳本需要在Windows和Linux上運行,所以理想情況下該解決方案只使用Python標準庫,而不使用任何特定於OS的調用。儘管如果我需要一個Windows代碼路徑和一個* nix代碼路徑(並且在我的代碼中有一個大的if語句來選擇其中一個或另一個),那麼如果「相同的代碼」解決方案不可行,那也沒關係。

我意識到我大概可以制定一種基於文件的方法(例如#1實例監視一個目錄中的更改,每個實例將文件放入該目錄中,但它需要工作),但我有點擔心在非正常關機後清理這些文件。我最好能夠使用內存中的解決方案。但是我也很靈活,如果一個基於持久性文件的方法是唯一的方法,我願意接受這個選擇。

更多細節:我試圖這樣做,因爲我們的服務器正在使用監視工具,它支持運行python腳本來收集監視工具然後索引的監視數據(例如數據庫查詢或Web服務調用的結果)以後使用。其中一些腳本的啓動非常昂貴,但啓動後運行起來很便宜(例如,使數據庫連接與運行查詢相比)。所以我們選擇讓它們在無限循環中運行,直到父進程殺死它們。

這很好,但是在較大型的服務器上,即使它們每隔20分鐘收集一次數據,同一腳本的100個實例也可能正在運行。這對RAM,數據庫連接限制等造成嚴重破壞。我們希望從1個線程的100個進程切換到100個線程的一個進程,每個線程執行之前一個腳本正在執行的工作。

但是改變腳本被監視工具調用的方式是不可能的。我們需要保持相同的調用(使用不同的命令行參數啓動一個進程),但是改變腳本以識別另一個腳本是活動的,並讓「新」腳本通過命令行參數發送它的工作指令到「舊」腳本。

順便說一句,這不是我想要在一個腳本的基礎上做的事情。相反,我想將這種行爲打包成一個許多腳本作者可以利用的庫 - 我的目標是使腳本作者可以編寫簡單的,單線程的腳本,這些腳本不會意識到多實例問題,並且可以處理多線程並在封面下單一實例化。

+0

爲什麼你堅持工作者腳本與命令調用腳本相同? worker腳本可以是一個服務器進程,它接收命令中繼客戶端發送的命令,由監視框架調用,它只有一項任務:告訴服務器它應該做什麼。 – Bernd 2010-05-29 17:09:35

回答

9

建立通信通道的Alex Martelli方法是合適的。我會使用一個multiprocessing.connection.Listener來創建一個監聽器,供您選擇。文檔位於: http://docs.python.org/library/multiprocessing.html#multiprocessing-listeners-clients

您可以選擇使用AF_UNIX for Linux和AF_PIPE for Windows,而不是使用AF_INET(套接字)。希望一個小小的「如果」不會受到傷害。

編輯:我想一個例子不會傷害。不過,這是最基本的。

#!/usr/bin/env python 

from multiprocessing.connection import Listener, Client 
import socket 
from array import array 
from sys import argv 

def myloop(address): 
    try: 
     listener = Listener(*address) 
     conn = listener.accept() 
     serve(conn) 
    except socket.error, e: 
     conn = Client(*address) 
     conn.send('this is a client') 
     conn.send('close') 

def serve(conn): 
    while True: 
     msg = conn.recv() 
     if msg.upper() == 'CLOSE': 
      break 
     print msg 
    conn.close() 

if __name__ == '__main__': 
    address = ('/tmp/testipc', 'AF_UNIX') 
    myloop(address) 

這適用於OS X,因此它需要在Linux和(在替換正確地址之後)Windows上進行測試。從安全角度來說,存在很多警告,主要的一點是conn.recv會取消其數據,因此幾乎總是使用recv_bytes。

+0

很好的回答!能夠使用命名管道(windows)或fifo(unix),因爲我可以在腳本之後命名管道/ fifo,這將是唯一的,比在腳本和端口號之間保持映射更容易。 – 2010-06-01 18:07:21

1

也許嘗試使用套接字進行通信?

9

一般方法是讓腳本在啓動時以確保排他性的方式設置通信通道(其他嘗試以可預測的方式設置相同通道失敗),以便進一步實例化腳本可以檢測到第一個正在運行的與之通話。

您對跨平臺功能的要求強烈指出使用套接字作爲有問題的通信通道:您可以指定一個爲您的腳本保留的「衆所周知的端口」,例如12345,並在該端口上打開一個套接字僅限本地主機(127.0.0.1)。如果嘗試打開該套接字失敗,因爲有問題的端口被「取走」,那麼您可以連接到該端口號,而這將允許您與現有腳本進行通信。

如果你不熟悉套接字編程,有一個很好的HOWTO文檔here。你也可以看看Python in a Nutshell的相關章節(當然,我對這個有偏見;-)。

+0

嗨亞歷克斯 - 感謝您的快速響應!我熟悉的端口方法的主要擔憂是衝突的可能性(我們不擁有服務器,所以其他程序可能會使用這些端口)和端口號管理(因爲我們將應用單實例技巧到由不同腳本作者維護的許多腳本)。有沒有解決上述問題的方法,或者我會用「命名的IPC」機制更好?我懷疑windows上的命名管道和* nix上的域套接字可以做到這一點,但我不知道他們從Python使用起來會有多容易。 – 2010-05-29 17:15:25

+0

@Justin,我不知道如何在跨平臺和「內在互斥」方式中使用諸如命名管道和unix域套接字之類的機制。爲了支持您識別的特定需求,您可以通過訪問和更新保存名稱的「.dbm」(或sqlite等)存檔來讓腳本記錄名稱X的腳本應該使用的「不太知名的端口」端口對應(如果啓動時的腳本在那裏沒有找到它的名字,它會從操作系統獲得一個新的端口並記錄它),也許使用一些文件鎖定機制來避免競爭條件。 – 2010-05-29 21:41:27

+0

@Muhammad Alkarouri的答案如下(使用多處理包)似乎是一個可行的跨平臺解決方案,同時避免了將腳本映射到端口號的複雜性。使用'multiprocessing'有什麼不好的地方? – 2010-06-01 18:15:55

0

聽起來像你最好的選擇是堅持一個PID文件,但它不僅包含進程ID - 它還包括先前實例正在監聽的端口號。因此,在啓動時檢查pid文件,如果存在,請查看是否正在運行具有該Id的進程 - 如果是,則將數據發送給它並退出,否則用當前進程的信息覆蓋該pid文件。