سؤال

أنا أعمل على تطبيق واجهة المستخدم الرسومية في WxPython، ولست متأكدًا من كيفية التأكد من تشغيل نسخة واحدة فقط من تطبيقي في أي وقت على الجهاز.نظرًا لطبيعة التطبيق، فإن تشغيله أكثر من مرة ليس له أي معنى، وسوف يفشل بسرعة.ضمن Win32، يمكنني ببساطة إنشاء كائن مزامنة مسمى والتحقق من ذلك عند بدء التشغيل.لسوء الحظ، لا أعرف أي مرافق في Linux يمكنها القيام بذلك.

أنا أبحث عن شيء سيتم إصداره تلقائيًا في حالة تعطل التطبيق بشكل غير متوقع.لا أرغب في تحميل المستخدمين عبء الاضطرار إلى حذف ملفات القفل يدويًا لأنني تعطلت.

هل كانت مفيدة؟

المحلول

هناك العديد من التقنيات الشائعة بما في ذلك استخدام الإشارات.الطريقة التي أراها مستخدمة في أغلب الأحيان هي إنشاء "ملف قفل معرف المنتج" عند بدء التشغيل والذي يحتوي على معرف معرف العملية قيد التشغيل.إذا كان الملف موجودًا بالفعل عند بدء تشغيل البرنامج، فافتحه واحصل على pid بداخله، وتحقق لمعرفة ما إذا كانت العملية التي تحتوي على pid قيد التشغيل، وإذا كانت كذلك، تحقق من قيمة cmdline في /proc/معرف المنتج لمعرفة ما إذا كان مثيلًا لبرنامجك، وإذا تم إنهاءه، وإلا فاستبدل الملف بمعرف التعريف الخاص بك.الاسم المعتاد لملف pid هو application_name.pid.

نصائح أخرى

الشيء الصحيح هو القفل الاستشاري باستخدام flock(LOCK_EX);في بايثون، تم العثور على هذا في fcntl وحدة.

على عكس ملفات pidfiles، يتم دائمًا تحرير هذه الأقفال تلقائيًا عندما تموت العملية الخاصة بك لأي سبب من الأسباب، ولا توجد حالات سباق تتعلق بحذف الملف (نظرًا لأن الملف لا يحتاج ليتم حذفه لتحرير القفل)، ولا توجد فرصة لوراثة معرف عملية مختلفة لعملية مختلفة وبالتالي الظهور وكأنها تتحقق من صحة القفل القديم.

إذا كنت تريد الكشف عن إيقاف التشغيل غير النظيف، فيمكنك كتابة علامة (مثل PID الخاص بك، للتقليديين) في الملف بعد الاستيلاء على القفل، ثم اقتطاع الملف إلى حالة 0 بايت قبل إيقاف التشغيل النظيف (أثناء تعليق القفل );وبالتالي، إذا لم يتم الاحتفاظ بالقفل وكان الملف غير فارغ، تتم الإشارة إلى إيقاف التشغيل غير النظيف.

حل القفل الكامل باستخدام fcntl وحدة:

import fcntl
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(1)

تقدم wxWidgets فئة wxSingleInstanceChecker لهذا الغرض: مستند wxPython, ، أو wxWidgets doc.يحتوي مستند wxWidgets على نموذج تعليمي برمجي بلغة C++، ولكن يجب أن يكون مكافئ python شيئًا كهذا (لم يتم اختباره):

  name = "MyApp-%s" % wx.GetUserId()
  checker = wx.SingleInstanceChecker(name)
  if checker.IsAnotherRunning():
      return False

هذا يعتمد على إجابة من قبل المستخدم zgoda.إنه يعالج بشكل أساسي مشكلة صعبة تتعلق بالوصول للكتابة إلى ملف القفل.على وجه الخصوص، إذا تم إنشاء ملف القفل لأول مرة بواسطة root, ، مستخدم آخر foo لم يعد بإمكانه بعد ذلك محاولة إعادة كتابة هذا الملف بنجاح بسبب عدم وجود أذونات الكتابة للمستخدم foo.يبدو أن الحل الواضح هو إنشاء الملف بأذونات الكتابة للجميع.يعتمد هذا الحل أيضًا على حل مختلف إجابة بواسطتي، يتعين علي إنشاء ملف بهذه الأذونات المخصصة.يعد هذا الاهتمام مهمًا في العالم الحقيقي حيث يمكن تشغيل برنامجك بواسطة أي مستخدم بما في ذلك root.

import fcntl, os, stat, tempfile

app_name = 'myapp'  # <-- Customize this value

# Establish lock file settings
lf_name = '.{}.lock'.format(app_name)
lf_path = os.path.join(tempfile.gettempdir(), lf_name)
lf_flags = os.O_WRONLY | os.O_CREAT
lf_mode = stat.S_IWUSR | stat.S_IWGRP | stat.S_IWOTH  # This is 0o222, i.e. 146

# Create lock file
# Regarding umask, see https://stackoverflow.com/a/15015748/832230
umask_original = os.umask(0)
try:
    lf_fd = os.open(lf_path, lf_flags, lf_mode)
finally:
    os.umask(umask_original)

# Try locking the file
try:
    fcntl.lockf(lf_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
except IOError:
    msg = ('Error: {} may already be running. Only one instance of it '
           'can run at a time.'
           ).format('appname')
    exit(msg)

يتمثل أحد قيود الكود أعلاه في أنه إذا كان ملف القفل موجودًا بالفعل بأذونات غير متوقعة، فلن يتم تصحيح هذه الأذونات.

كنت أرغب في استخدام /var/run/<appname>/ كدليل لملف القفل، ولكن إنشاء هذا الدليل يتطلب root الأذونات.يمكنك اتخاذ القرار الخاص بك بشأن الدليل الذي ستستخدمه.

لاحظ أنه ليست هناك حاجة لفتح مؤشر ملف لملف القفل.

إليك الحل المعتمد على منفذ TCP:

# Use a listening socket as a mutex against multiple invocations
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 5080))
s.listen(1)

ابحث عن وحدة python التي تتفاعل مع إشارات SYSV على نظام التشغيل Unix.تحتوي الإشارات على علامة SEM_UNDO والتي ستتسبب في تحرير الموارد التي تحتفظ بها العملية في حالة تعطل العملية.

خلاف ذلك، كما اقترح برنارد، يمكنك استخدام

import os
os.getpid()

واكتبه إلى /var/run/application_name.pid.عندما تبدأ العملية، يجب التحقق مما إذا كان الرقم التعريفي موجود في /var/run/application_nameيتم إدراج .pid في جدول ps ويتم الخروج منه إذا كان كذلك، وإلا فاكتب pid الخاص به في /var/run/application_name.pid.في var_run_pid التالي هو الرقم التعريفي الذي قرأته من /var/run/application_name.pid

cmd = "ps -p %s -o comm=" % var_run_pid
app_name = os.popen(cmd).read().strip()
if len(app_name) > 0:
    Already running

مجموعة الوظائف المحددة في semaphore.h -- sem_open(), sem_trywait(), ، وما إلى ذلك - أعتقد أنها تعادل POSIX.

إذا قمت بإنشاء ملف قفل ووضعت معرف الهوية فيه، فيمكنك التحقق من معرف العملية الخاص بك مقابله ومعرفة ما إذا كنت قد تعطلت، أليس كذلك؟

لم أفعل ذلك شخصيًا، لذا تناولي كميات مناسبة من الملح.:ص

هل يمكنك استخدام الأداة المساعدة "pidof"؟إذا كان تطبيقك قيد التشغيل، فسوف يكتب pidof معرف العملية لتطبيقك إلى stdout.إذا لم يكن الأمر كذلك، فسيتم طباعة سطر جديد (LF) وإرجاع رمز الخطأ.

مثال (من باش، للبساطة):

linux# pidof myapp
8947
linux# pidof nonexistent_app

linux#

الطريقة الأكثر شيوعًا هي إسقاط ملف في /var/run/ يسمى [application].pid والذي يحتوي فقط على معرف العملية الجاري تشغيلها، أو العملية الأصلية.كبديل، يمكنك إنشاء أنبوب مسمى في نفس الدليل لتتمكن من إرسال رسائل إلى العملية النشطة، على سبيل المثال.لفتح ملف جديد.

لقد قمت بإنشاء إطار عمل أساسي لتشغيل هذه الأنواع من التطبيقات عندما تريد أن تكون قادرًا على تمرير وسيطات سطر الأوامر للمثيلات التي تمت تجربتها لاحقًا إلى الحالة الأولى.سيبدأ المثيل في الاستماع على منفذ محدد مسبقًا إذا لم يعثر على مثيل يستمع هناك بالفعل.إذا كان المثيل موجودًا بالفعل، فإنه يرسل وسيطات سطر الأوامر الخاصة به عبر المقبس ويخرج.

الكود مع الشرح

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top