2008-10-21 19 views
30

Tôi đang làm việc trên ứng dụng GUI trong WxPython và tôi không chắc chắn làm thế nào tôi có thể đảm bảo rằng chỉ có một bản sao ứng dụng của tôi đang chạy tại bất kỳ thời điểm nào trên máy. Do tính chất của ứng dụng, chạy nhiều lần không có ý nghĩa gì và sẽ thất bại nhanh chóng. Theo Win32, tôi có thể chỉ cần tạo một mutex có tên và kiểm tra xem khi khởi động. Thật không may, tôi không biết bất kỳ cơ sở nào trong Linux có thể làm điều này.Đảm bảo một cá thể của một ứng dụng trong Linux

Tôi đang tìm kiếm thứ gì đó sẽ tự động được phát hành nếu ứng dụng gặp sự cố bất ngờ. Tôi không muốn phải gánh nặng người dùng của mình bằng việc phải xóa các tệp khóa theo cách thủ công vì tôi đã gặp sự cố.

Trả lời

23

Có một số kỹ thuật phổ biến bao gồm sử dụng các ẩn dụ. Cái tôi thấy thường được sử dụng nhất là tạo một "tệp khóa pid" khi khởi động có chứa pid của tiến trình đang chạy. Nếu tập tin đã tồn tại khi chương trình khởi động, mở nó lên và lấy pid bên trong, kiểm tra xem liệu một tiến trình với pid đang chạy, nếu nó kiểm tra giá trị cmdline trong/proc/pid để xem nó có là một thể hiện của chương trình của bạn, nếu nó là sau đó bỏ thuốc lá, nếu không ghi đè lên các tập tin với pid của bạn. Tên thông thường cho tệp pid là application_name.pid.

+1

Theo quy ước, điều này nên đi theo/var/run /, đúng không? –

+0

Trong sự tò mò, không chỉ đơn giản là mở tập tin để truy cập độc quyền làm công việc của một mutex? – Menkboy

+0

Menkboy, nếu tệp được đóng lại đúng cách trong trường hợp xảy ra sự cố, thì tôi nghĩ điều đó sẽ hoạt động hoàn hảo và đơn giản hóa mọi thứ hơn nữa. Cảm ơn bạn. –

0

Nếu bạn tạo tệp khóa và đặt pid vào đó, bạn có thể kiểm tra id tiến trình của bạn dựa vào nó và cho biết bạn có bị lỗi không?

Tôi đã không thực hiện việc này một cách cá nhân, vì vậy hãy uống một lượng muối thích hợp. : p

+2

Sử dụng tập tin PID là phổ biến, nhưng là không phải không có vấn đề. Đối với một, có thể có điều kiện chủng tộc. Thứ hai, nó có thể không được dọn dẹp nếu ứng dụng bị giết. –

0

Bạn có thể sử dụng tiện ích 'pidof' không? Nếu ứng dụng của bạn đang chạy, pidof sẽ viết ID tiến trình của ứng dụng của bạn để xuất bản. Nếu không, nó sẽ in một dòng mới (LF) và trả về một mã lỗi.

Ví dụ (từ bash, vì đơn giản):

linux# pidof myapp 
8947 
linux# pidof nonexistent_app 

linux# 
+0

Đây là gọn gàng, ít mã yêu cầu hơn so với phương pháp tiếp cận lockfile. Nhược điểm là nó không phải là chính xác nguyên tử, nhưng đối với phát hiện cá thể duy nhất, có thể không cần thiết. –

+0

Điều này sẽ không hoạt động nếu bạn đang chạy "ứng dụng pidof" từ bên trong ứng dụng – MattSmith

+3

Nó cũng sẽ không hoạt động nếu có một chương trình đang chạy khác có cùng tên hoặc nếu người dùng khác cũng đang chạy chương trình. – CesarB

0

Cho đến nay phương pháp phổ biến nhất là thả một tập tin vào thư mục/var/run/gọi [ứng dụng] .pid mà chỉ chứa các PID của quá trình chạy hoặc quá trình cha mẹ. Thay vào đó, bạn có thể tạo một đường ống có tên trong cùng một thư mục để có thể gửi thư đến quá trình hoạt động, ví dụ: để mở một tệp mới.

+0

Sử dụng tệp PID là phổ biến, nhưng không phải là không có vấn đề. Đối với một, có thể có điều kiện chủng tộc. Thứ hai, nó có thể không được dọn dẹp nếu ứng dụng bị giết. –

1

Tìm một mô-đun python giao diện với các semaphores SYSV trên Unix. Các semaphores có một lá cờ SEM_UNDO mà sẽ gây ra các nguồn lực được tổ chức bởi một quá trình được phát hành nếu quá trình bị treo.

Nếu như Bernard gợi ý, bạn có thể sử dụng

import os 
os.getpid() 

Và ghi nó vào/var/run/application_name .pid. Khi quá trình bắt đầu, nó nên kiểm tra xem pid trong/var/run/application_name .pid được liệt kê trong bảng ps và thoát nếu nó là, nếu không ghi pid riêng của nó vào/var/run/application_name .pid . Trong var_run_pid Sau đây là pid bạn đọc từ/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 
+0

+1 để đề xuất các semaphores SYSV, -1 cho đề xuất gọi 'ps' thay vì một cái gì đó hiệu quả hơn (nói,' kill -0' - thông qua lệnh gọi tín hiệu thay vì lệnh nếu người ta muốn tránh thêm một ngã ba/exec) và đề xuất các pidfiles không có khóa cố vấn (ví dụ đàn) để tránh điều kiện chủng tộc và va chạm PID. –

58

The Right Thing là khóa tư vấn sử dụng flock(LOCK_EX); bằng Python, được tìm thấy trong số fcntl module.

Không giống như pidfiles, các khóa này luôn tự động được giải phóng khi quá trình của bạn bị chết vì bất kỳ lý do gì, không tồn tại điều kiện chủng tộc liên quan đến việc xóa tệp. không có cơ hội của một quá trình khác nhau kế thừa PID và do đó xuất hiện để xác nhận một khóa cũ.

Nếu bạn muốn phát hiện tắt không sạch, bạn có thể viết điểm đánh dấu (chẳng hạn như PID, cho người theo chủ nghĩa truyền thống) vào tệp sau khi lấy khóa và sau đó cắt tệp thành trạng thái 0 byte trước khi tắt máy sạch (trong khi khóa đang được giữ); do đó, nếu khóa không được giữ và tập tin là không trống, một tắt máy ô uế được chỉ định.

+1

Tôi đồng ý, đây có lẽ là phương pháp tốt nhất trong hầu hết các trường hợp. –

+1

Đây là phương pháp chính xác để khóa, tuy nhiên nó cũng tốt để viết một tập tin PID được làm sạch lên trên lối ra bình thường, cũng như để tạo ra một mục trong/var/lock/subsys (nếu nó tồn tại). Điều này cho phép chương trình của bạn để nhận ra nếu nó lại bắt đầu từ một vụ tai nạn, trong số những thứ khác. Vì vậy, làm cả hai giúp. –

+0

@tinkertim - Không phải là một gợi ý xấu, mặc dù nó có ý nghĩa để flock() pidfile hơn là có nhiều hơn một. –

24

Hoàn thành giải pháp khóa bằng cách sử dụng mô-đun 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) 
+2

Giả sử tệp khóa giống nhau đối với tất cả người dùng, vì nó phải là để khóa hữu ích, điều này có thể tạo ra sự cố cấp quyền ghi. Tôi đã mô tả và giải quyết vấn đề này trong một câu trả lời. –

+0

Tôi không biết tại sao nhưng điều này không hoạt động trong Python 3.4.1. Hai trường hợp chạy mà không có bất kỳ lỗi nào. – boreq

+0

@boreq, bạn có thể rõ ràng hơn một chút về chính xác cách bạn đang thử nghiệm (hệ thống tệp đang sử dụng, hành vi mong đợi, hành vi thực tế, v.v ...) không? Ví dụ: –

8

wxWidgets cung cấp một lớp wxSingleInstanceChecker cho mục đích này: wxPython doc, hoặc wxWidgets doc. WxWidgets doc có mẫu mã trong C++, nhưng trăn tương đương nên một cái gì đó như thế này (chưa được kiểm tra):

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

Tôi đã thực hiện một khuôn khổ cơ bản cho chạy các loại ứng dụng khi bạn muốn để có thể vượt qua các đối số dòng lệnh của các phiên bản đã thử tiếp theo cho phiên bản đầu tiên. Một thể hiện sẽ bắt đầu lắng nghe trên một cổng được xác định trước nếu nó không tìm thấy một thể hiện đã lắng nghe ở đó. Nếu một cá thể đã tồn tại, nó sẽ gửi các đối số dòng lệnh của nó qua socket và thoát.

code w/ explanation

6

này được dựa trên các answer của thành viên zgoda. Nó chủ yếu giải quyết một mối quan tâm khó khăn phải làm với ghi truy cập vào tập tin khóa. Đặc biệt, nếu tệp khóa được tạo lần đầu tiên bởi root, một người dùng khác foo có thể không còn cố gắng viết lại tệp này thành công nữa do thiếu quyền ghi cho người dùng foo. Giải pháp hiển nhiên dường như là tạo tệp có quyền ghi cho mọi người. Giải pháp này cũng xây dựng dựa trên một khác nhau answer của tôi, phải làm việc tạo một tập tin với quyền tùy chỉnh như vậy. Mối quan tâm này rất quan trọng trong thế giới thực nơi chương trình của bạn có thể được điều hành bởi bất kỳ người dùng nào kể cả 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) 

Hạn chế của mã trên là nếu tệp khóa đã tồn tại với quyền không mong muốn, các quyền đó sẽ không được sửa.

Tôi đã thích sử dụng /var/run/<appname>/ làm thư mục cho tệp khóa nhưng việc tạo thư mục này yêu cầu quyền đối với root. Bạn có thể đưa ra quyết định của riêng mình để sử dụng thư mục nào.

Lưu ý rằng không cần mở trình xử lý tệp vào tệp khóa.

4

Đây là giải pháp TCP port-based:

# 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) 
Các vấn đề liên quan