ตรวจสอบให้แน่ใจว่ามีโปรแกรมทำงานอยู่เพียงอินสแตนซ์เดียว


120

มีวิธี Pythonic ที่จะมีโปรแกรมทำงานเพียงอินสแตนซ์เดียวหรือไม่?

วิธีแก้ปัญหาที่สมเหตุสมผลเพียงวิธีเดียวที่ฉันคิดขึ้นมาคือพยายามเรียกใช้เป็นเซิร์ฟเวอร์ในบางพอร์ตจากนั้นโปรแกรมที่สองพยายามผูกกับพอร์ตเดียวกัน - ล้มเหลว แต่มันก็ไม่ใช่ความคิดที่ดีจริงๆอาจจะมีอะไรที่เบากว่านี้?

(โปรดพิจารณาว่าโปรแกรมที่คาดว่าจะล้มเหลวในบางครั้งเช่น segfault ดังนั้นสิ่งต่างๆเช่น "ล็อกไฟล์" จะไม่ทำงาน)


1
บางทีชีวิตของคุณอาจจะง่ายขึ้นถ้าคุณติดตามและแก้ไขข้อผิดพลาด ไม่ใช่ว่าเป็นเรื่องง่ายที่จะทำ
David Locke

มันไม่ได้อยู่ในห้องสมุดของฉันมันอยู่ในการผูก libxml ของ python และขี้อายมาก - ยิงเพียงสองสามวัน
Slava V

5
ไลบรารีมาตรฐานของ Python รองรับ flock () ซึ่งเป็นสิ่งที่เหมาะสมสำหรับโปรแกรม UNIX สมัยใหม่ การเปิดพอร์ตจะใช้สปอตในเนมสเปซที่มีข้อ จำกัด มากกว่าในขณะที่พิดไฟล์มีความซับซ้อนมากขึ้นเนื่องจากคุณต้องตรวจสอบกระบวนการที่กำลังทำงานอยู่เพื่อทำให้ไม่ถูกต้องอย่างปลอดภัย ฝูงไม่มีปัญหา
Charles Duffy

s / UNIX / linux / เอาล่ะ FTFY
kaleissin

คำตอบ:


101

รหัสต่อไปนี้ควรใช้งานได้คือข้ามแพลตฟอร์มและทำงานบน 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 กรุณาโรคจิตแฟ้มได้ที่นี่

คุณสามารถติดตั้งได้โดยใช้วิธีใดวิธีหนึ่งต่อไปนี้:


2
ฉันอัปเดตคำตอบและรวมลิงก์ไปยังเวอร์ชันล่าสุด หากคุณพบข้อผิดพลาดโปรดส่งไปที่ github และเราจะแก้ไขโดยเร็วที่สุด
sorin

2
@Johny_M ขอบคุณฉันทำแพทช์และเปิดตัวรุ่นใหม่ในpypi.python.org/pypi/tendo
โซริน

2
ไวยากรณ์นี้ใช้ไม่ได้สำหรับฉันบน windows ภายใต้ Python 2.6 สิ่งที่ใช้ได้ผลสำหรับฉันคือ 1: จาก tendo import singleton 2: me = singleton.SingleInstance ()
Brian

25
การพึ่งพาอื่นสำหรับบางสิ่งที่เล็กน้อยเช่นนี้? ฟังดูไม่น่าดึงดูดเท่าไหร่
WhyNotHugo

2
Singleton จัดการกระบวนการที่ได้รับซิกเทอร์มหรือไม่ (เช่นหากกระบวนการทำงานนานเกินไป) หรือฉันต้องจัดการกับสิ่งนั้นหรือไม่
JimJty

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 หรือไม่?
Joachim Sauer

1
ไม่มีfcntlโมดูลบน Windows (แม้ว่าจะสามารถจำลองฟังก์ชันการทำงานได้)
jfs

10
เคล็ดลับ: หากคุณต้องการรวมสิ่งนี้ในฟังก์ชัน 'fp' ต้องเป็นโกลบอลมิฉะนั้นไฟล์จะถูกปิดหลังจากออกจากฟังก์ชัน
cmcginty

1
@Mirko Control + Z ไม่ออกจากแอปพลิเคชัน (บนระบบปฏิบัติการใด ๆ ที่ฉันทราบ) จะระงับแอปพลิเคชัน fgโปรแกรมประยุกต์ที่สามารถกลับไปเบื้องหน้าด้วย ดังนั้นดูเหมือนว่าจะทำงานได้อย่างถูกต้องสำหรับคุณ (เช่นแอปยังทำงานอยู่ แต่ถูกระงับดังนั้นการล็อกจึงยังคงอยู่ในตำแหน่ง)
Sam Bull

1
รหัสนี้ในสถานการณ์ของฉัน (Python 3.8.3 บน Linux) ต้องการ modyfication: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 ยังคงอยู่หลังจากนี้และสิ่งนี้ทำลายความคิดทั้งหมด วิธีแก้ปัญหาข้างต้นด้วยการรับการล็อคชื่อไฟล์เฉพาะนั้นเชื่อถือได้มากในกรณีนี้
Danylo Gurianov

2
ตามที่ระบุไว้ข้างต้นโซลูชันนี้ใช้ได้กับ Linux แต่ใช้ไม่ได้กับ Mac OS X
Bilal และ Olga

2
วิธีนี้ใช้ไม่ได้ ฉันลองบน Ubuntu 14.04 เรียกใช้สคริปต์เดียวกันจาก 2 เทอร์มินัล windows พร้อมกัน ทั้งคู่ทำงานได้ดี
Dimon

1
สิ่งนี้ใช้ได้ผลสำหรับฉันใน Ubuntu 16 และการฆ่ากระบวนการไม่ว่าด้วยวิธีใดก็ตามทำให้กระบวนการอื่นเริ่มต้นได้ Dimon ฉันคิดว่าคุณทำอะไรผิดในการทดสอบของคุณ (บางทีคุณอาจลืมที่จะทำให้สคริปต์ของคุณเข้าสู่โหมดสลีปหลังจากที่โค้ดด้านบนทำงานดังนั้นมันจึงออกจากซ็อกเก็ตทันที)
ลุค

1
ไม่ใช่คำถามของการนอนหลับ รหัสใช้งานได้ แต่เป็นรหัสแบบอินไลน์เท่านั้น ฉันใส่มันลงในฟังก์ชัน ซ็อกเก็ตหายไปทันทีที่มีฟังก์ชันนี้อยู่
Steve Cohen

25

ฉันไม่รู้ว่ามันเป็น pythonic เพียงพอหรือไม่ แต่ในโลกของ Java ที่ฟังบนพอร์ตที่กำหนดนั้นเป็นโซลูชันที่ใช้กันอย่างแพร่หลายเนื่องจากทำงานบนแพลตฟอร์มหลัก ๆ ทั้งหมดและไม่มีปัญหากับโปรแกรมที่หยุดทำงาน

ข้อดีอีกอย่างของการฟังพอร์ตคือคุณสามารถส่งคำสั่งไปยังอินสแตนซ์ที่กำลังทำงานอยู่ ตัวอย่างเช่นเมื่อผู้ใช้เริ่มโปรแกรมเป็นครั้งที่สองคุณสามารถส่งคำสั่งให้อินสแตนซ์ที่กำลังทำงานอยู่เพื่อบอกให้เปิดหน้าต่างอื่นได้ (นั่นคือสิ่งที่ Firefox ทำฉันไม่รู้ว่าพวกเขาใช้พอร์ต TCP หรือไปป์ที่มีชื่อหรือ อะไรทำนองนั้น 'แม้ว่า)


+1 โดยเฉพาะอย่างยิ่งเนื่องจากอนุญาตให้ฉันแจ้งเตือนอินสแตนซ์ที่กำลังทำงานอยู่ดังนั้นมันจึงสร้างหน้าต่างขึ้นมาใหม่ปรากฏขึ้นและอื่น ๆ
WhyNotHugo

1
ใช้เช่นimport socket; s = socket.socket(socket.AF_INET, socket.SOCK_STREAM); s.bind(('localhost', DEFINED_PORT)). OSErrorจะเพิ่มขึ้นถ้ากระบวนการอื่นถูกผูกไว้กับพอร์ตเดียวกัน
Crishoj

13

ไม่เคยเขียน python มาก่อน แต่นี่คือสิ่งที่ฉันเพิ่งใช้ใน mycheckpoint เพื่อป้องกันไม่ให้ crond เริ่มต้นสองครั้งหรือมากกว่า:

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) และรักษาการล็อกจนกว่าสคริปต์จะสิ้นสุด (ปกติหรือข้อผิดพลาด)


1
สง่างามมาก. ฉันเปลี่ยนมันเพื่อให้ได้เส้นทางจากอาร์กิวเมนต์ของสคริปต์ แนะนำให้ฝังสิ่งนี้ไว้ในสถานที่ทั่วไปเช่น
Jossef Harush

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
นี้มีสภาพการแข่งขัน ลำดับการทดสอบแล้วเขียนอาจทำให้เกิดข้อยกเว้นของสองโปรแกรมที่เริ่มต้นเกือบพร้อมกันไม่พบไฟล์และพยายามเปิดเพื่อเขียนพร้อมกัน มันควรจะเพิ่มการยกเว้นในหนึ่งที่ช่วยให้คนอื่น ๆ ที่จะดำเนินการ
ล็อตต์

6

คุณพบคำตอบสำหรับคำถามที่คล้ายกันในเธรดอื่นแล้วดังนั้นเพื่อความสมบูรณ์โปรดดูวิธีการบรรลุสิ่งเดียวกันบน Windows โดยใช้ชื่อ mutex

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


5

วิธีนี้อาจใช้ได้ผล

  1. พยายามสร้างไฟล์ PID ไปยังตำแหน่งที่รู้จัก หากคุณล้มเหลวมีคนล็อกไฟล์คุณเสร็จแล้ว

  2. เมื่อคุณเสร็จสิ้นตามปกติให้ปิดและลบไฟล์ PID เพื่อให้คนอื่นเขียนทับได้

คุณสามารถรวมโปรแกรมของคุณในเชลล์สคริปต์ที่ลบไฟล์ PID แม้ว่าโปรแกรมของคุณจะขัดข้อง

คุณยังสามารถใช้ไฟล์ PID เพื่อฆ่าโปรแกรมได้หากแฮงค์


3

การใช้ไฟล์ล็อคเป็นวิธีการทั่วไปในยูนิกซ์ หากเกิดปัญหาคุณต้องล้างข้อมูลด้วยตนเอง คุณสามารถจัดเก็บ PID ไว้ในไฟล์และเมื่อเริ่มต้นตรวจสอบว่ามีกระบวนการกับ PID นี้หรือไม่โดยจะแทนที่ไฟล์ล็อกถ้าไม่ (อย่างไรก็ตามคุณต้องมีการล็อกรอบไฟล์ read-file-check-pid-rewrite-file) คุณจะพบสิ่งที่คุณต้องการสำหรับการรับและตรวจสอบ pid ในos -package วิธีทั่วไปในการตรวจสอบว่ามีกระบวนการกับ pid ที่กำหนดหรือไม่คือการส่งสัญญาณที่ไม่ร้ายแรง

ทางเลือกอื่นอาจรวมเข้ากับฝูงหรือเซมาโฟร์

การเปิดซ็อกเก็ตเครือข่ายตามที่ saua เสนอน่าจะเป็นวิธีที่ง่ายและพกพาสะดวกที่สุด


3

สำหรับใครที่ใช้wxPythonสำหรับการประยุกต์ใช้ของพวกเขาคุณสามารถใช้ฟังก์ชั่น การบันทึกไว้ที่นี่wx.SingleInstanceChecker

ผมเองใช้ subclass ของwx.Appซึ่งทำให้การใช้wx.SingleInstanceCheckerและผลตอบแทนFalseจากOnInit()ถ้ามีอินสแตนซ์ที่มีอยู่ของแอปแล้วการดำเนินการเช่นดังนั้น:

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

หลังจากเข้ารหัสเธรดรายชื่อซ็อกเก็ตสำหรับซิงเกิลตันฉันพบสิ่งนี้ซึ่งใช้งานได้ดีและฉันได้ติดตั้งโปรแกรมไว้แล้วอย่างไรก็ตามฉันต้องการ "ปลุก" เพิ่มเติมฉันสามารถให้ซิงเกิลตันเพื่อที่ฉันจะได้นำมันไปที่ ด้านหน้าและตรงกลางของหน้าต่างบานใหญ่ที่ซ้อนทับกัน นอกจากนี้: ลิงก์ "มีเอกสารที่นี่" ชี้ไปยังเอกสารที่สร้างขึ้นโดยอัตโนมัติที่ไร้ประโยชน์นี่เป็นลิงก์ที่ดีกว่า
RufusVS

@RufusVS คุณพูดถูก - นั่นคือลิงค์เอกสารที่ดีกว่ามากอัปเดตคำตอบแล้ว
Matt Coubrough

3

นี่คือโซลูชัน 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)

คำอธิบาย

โค้ดพยายามสร้าง mutex ด้วยชื่อที่ได้มาจากพา ธ แบบเต็มไปยังสคริปต์ เราใช้เครื่องหมายทับเพื่อหลีกเลี่ยงความสับสนที่อาจเกิดขึ้นกับระบบไฟล์จริง

ข้อดี

  • ไม่จำเป็นต้องกำหนดค่าหรือตัวระบุ 'เวทย์มนตร์' ใช้มันในสคริปต์ต่างๆได้มากเท่าที่จำเป็น
  • ไม่มีไฟล์เก่าค้างอยู่ mutex ตายไปพร้อมกับคุณ
  • พิมพ์ข้อความที่เป็นประโยชน์เมื่อรอ

3

ทางออกที่ดีที่สุดสำหรับ windows คือใช้ mutexes ตามที่ @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(รวมอยู่ในแพ็คเกจ @sorin tendo) ซึ่งไม่มีใน windows และหากคุณพยายามหยุดแอป python ของคุณโดยใช้แพ็คเกจpyinstallerที่นำเข้าแบบคงที่จะทำให้เกิดข้อผิดพลาด

นอกจากนี้การใช้วิธีการล็อกไฟล์ทำให้read-onlyเกิดปัญหากับไฟล์ฐานข้อมูล (พบปัญหานี้ด้วยsqlite3)


2

ฉันโพสต์ข้อความนี้เป็นคำตอบเพราะฉันเป็นผู้ใช้ใหม่และ Stack Overflow ยังไม่ยอมให้ฉันโหวต

โซลูชันของ Sorin Sbarnea ใช้ได้กับฉันภายใต้ OS X, Linux และ Windows และฉันรู้สึกขอบคุณสำหรับมัน

อย่างไรก็ตาม tempfile.gettempdir () ทำงานทางเดียวภายใต้ OS X และ Windows และอีกวิธีหนึ่งภายใต้ nixes บาง / หลาย / ทั้งหมด (?) * (โดยไม่สนใจข้อเท็จจริงที่ว่า OS X เป็น Unix ด้วย!) ความแตกต่างมีความสำคัญต่อรหัสนี้

OS X และ Windows มีไดเร็กทอรี temp เฉพาะผู้ใช้ดังนั้น tempfile ที่สร้างโดยผู้ใช้รายหนึ่งจะไม่ปรากฏแก่ผู้ใช้รายอื่น ในทางตรงกันข้ามภายใต้ * nix หลายเวอร์ชัน (ฉันทดสอบ Ubuntu 9, RHEL 5, OpenSolaris 2008 และ FreeBSD 8) อุณหภูมิ dir คือ / tmp สำหรับผู้ใช้ทุกคน

นั่นหมายความว่าเมื่อสร้างไฟล์ล็อกบนเครื่องที่มีผู้ใช้หลายคนไฟล์นั้นจะถูกสร้างขึ้นใน / tmp และมีเพียงผู้ใช้ที่สร้างไฟล์ล็อกในครั้งแรกเท่านั้นที่จะสามารถเรียกใช้แอปพลิเคชันได้

วิธีแก้ไขที่เป็นไปได้คือการฝังชื่อผู้ใช้ปัจจุบันในชื่อไฟล์ล็อก

เป็นที่น่าสังเกตว่าโซลูชันของ OP ในการคว้าพอร์ตจะทำงานผิดปกติในเครื่องที่มีผู้ใช้หลายคน


สำหรับผู้อ่านบางคน (เช่นฉัน) พฤติกรรมที่ต้องการคือสำเนาเพียงชุดเดียวเท่านั้นที่สามารถเรียกใช้ช่วงเวลาโดยไม่คำนึงถึงจำนวนผู้ใช้ที่เกี่ยวข้อง ดังนั้นไดเร็กทอรี tmp ต่อผู้ใช้จึงเสียในขณะที่แชร์ / tmp หรือพอร์ตล็อกแสดงพฤติกรรมที่ต้องการ
Jonathan Hartley

2

ฉันใช้single_processกับ gentoo ของฉัน

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

ใน Windows ฉันได้รับ: ImportError: ไม่มีโมดูลชื่อ fcntl
Andrew W. Phillips

1

ฉันยังคงสงสัยว่าควรจะเป็นโซลูชัน POSIXy ที่ดีโดยใช้กลุ่มกระบวนการโดยไม่ต้องกดระบบไฟล์ แต่ฉันไม่สามารถตอกมันลงได้ สิ่งที่ต้องการ:

เมื่อเริ่มต้นกระบวนการของคุณจะส่ง 'kill -0' ไปยังกระบวนการทั้งหมดในกลุ่มเฉพาะ หากมีกระบวนการดังกล่าวก็จะออก จากนั้นจะเข้าร่วมกลุ่ม ไม่มีกระบวนการอื่นใดใช้กลุ่มนั้น

อย่างไรก็ตามสิ่งนี้มีเงื่อนไขการแข่งขัน - กระบวนการหลายอย่างสามารถทำสิ่งนี้ได้ในเวลาเดียวกันอย่างแม่นยำและทั้งหมดลงเอยด้วยการเข้าร่วมกลุ่มและทำงานพร้อมกัน เมื่อคุณเพิ่ม mutex บางประเภทเพื่อให้กันน้ำได้คุณก็ไม่ต้องใช้กลุ่มกระบวนการอีกต่อไป

สิ่งนี้อาจยอมรับได้หากกระบวนการของคุณเริ่มต้นโดย 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: ไม่สามารถนำเข้าชื่อ" singleton " นี่คือบน Mac
grayaii

ปรากฎว่า Quicklock ใช้ไม่ได้กับ python 3 นั่นคือเหตุผลที่ทำให้ฉันล้มเหลว
grayaii

ใช่ขออภัยมันไม่ได้รับการพิสูจน์ในอนาคตเลย ฉันยินดีต้อนรับการมีส่วนร่วมเพื่อให้มันใช้งานได้!
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

เราจำเป็นต้องกำหนด global SOCKETvaiable เนื่องจากขยะจะถูกเก็บเมื่อกระบวนการทั้งหมดหยุดลงเท่านั้น หากเราประกาศตัวแปรโลคัลในฟังก์ชันตัวแปรนั้นจะอยู่นอกขอบเขตหลังจากฟังก์ชันออกจากระบบดังนั้นซ็อกเก็ตจะถูกลบ

เครดิตทั้งหมดควรไปที่ Roberto Rosario เนื่องจากฉันเพียงชี้แจงและอธิบายรายละเอียดเกี่ยวกับรหัสของเขา และโค้ดนี้จะใช้งานได้เฉพาะบน Linux เท่านั้นดังที่อ้างถึงต่อไปนี้จากhttps://troydhanson.github.io/network/Unix_domain_sockets.htmlอธิบาย:

Linux มีคุณสมบัติพิเศษ: ถ้าชื่อพา ธ สำหรับซ็อกเก็ตโดเมน UNIX ขึ้นต้นด้วย null byte \ 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

เธรดนี้ปลอดภัยหรือไม่? ดูเหมือนว่าการตรวจสอบและการสร้างไฟล์ temp ไม่ใช่ atomic ...
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.