Как проверить, запущен ли уже другой экземпляр приложения/двоичного файла

Я пишу приложение командной строки на Mac, используя Objective-c

В начале приложения я хочу проверить, запущен ли уже другой экземпляр того же приложения. Если это так, то я должен либо дождаться его завершения, либо выйти из текущего экземпляра, либо выйти из другого экземпляра и т. д.

Есть ли способ сделать это?


person Renjith    schedule 30.05.2012    source источник


Ответы (3)


Стандартным решением Unix для этого является создание «файла запуска». При запуске вы пытаетесь создать этот файл и записать в него свой pid, если он не существует; если он существует, прочитайте из него pid, и если есть работающая программа с этим pid и именем вашего процесса, подождите/выйдите/что угодно.

Вопрос в том, куда вы помещаете этот файл и как вы его называете?

Ну, во-первых, вы должны решить, что именно означает «уже работает». Очевидно, не «где угодно в мире», но это может быть что угодно, от «где угодно на текущей машине» до «в текущем сеансе рабочего стола». (Например, если пользователь А запускает вашу программу, приостанавливает ее, затем появляется пользователь Б и берет на себя управление компьютером с помощью быстрого переключения пользователей, должен ли он запускать программу или нет?)

Практически для любого разумного ответа на этот вопрос существует очевидный шаблон имени пути. Например, на Mac /tmp является общим для всей системы, а $TMPDIR зависит от конкретного сеанса, поэтому, например, /tmp/${ARGV[0]}.pid — это хороший способ сказать «только один копировать на машину, точка", а ${TMPDIR}/${ARGV[0]}.pid - хороший способ сказать "только одна копия за сеанс".

person abarnert    schedule 31.05.2012
comment
Спасибо, я уже сделал это подобным образом, я думал, что будет лучший/прямой способ. Я объясню, как я это сделал ниже. где угодно на текущей машине это то, что мне было нужно. - person Renjith; 31.05.2012
comment
@abarnert У тебя нет состояния гонки? В обычных случаях это маловероятно, но если процесс запускается часто/автоматически, вероятность состояния гонки выше. - person Breaking not so bad; 31.05.2012
comment
@ ring0: да, наивная реализация имеет состояние гонки, но есть несколько способов обойти это. По сути, все они предполагают оптимистичные действия и повторение цикла в случае неожиданного сбоя. Например, вы можете выполнить atomic-write-temp-and-rename, возясь с разрешениями, поэтому, если два процесса попытаются выполнить оба, один из них завершится ошибкой и повторит попытку. Или вы можете просто создать и записать файл на место; один процесс может видеть существующий файл, но не видеть никакого содержимого, и в этом случае он должен повторить попытку. И так далее. - person abarnert; 31.05.2012
comment
Единственная проблема, о которой не заботятся большинство распространенных решений, заключается в том, что процесс может умереть, оставив свой pid-файл, а затем позже запустятся две новые копии, и одна из них будет иметь тот же pid, что и заброшенная. Вы можете справиться с этим, написав pid плюс отметку времени запуска, но тогда вам нужен способ получить время запуска для другого процесса, что сложнее сделать кросс-платформенным… - person abarnert; 31.05.2012
comment
Если вы делаете это, не возитесь с PID. Просто используйте flock для получения эксклюзивной блокировки файла. Только один процесс может получить блокировку. Если блокировка не удалась, она досталась другому процессу. Когда этот процесс умирает, блокировка снимается. - person Mike Weller; 13.08.2012
comment
@MikeWeller: ОП хочет иметь возможность делать такие вещи, как выход из исходного экземпляра. Как ты собираешься справиться с этим со стадом? Вы получаете EWOULDBLOCK и знаете, что кто-то его заблокировал, так что… что теперь? Без пид ничего не сделаешь. - person abarnert; 14.08.2012
comment
Ах, простите, мой мозг, видимо, решил проигнорировать это требование. - person Mike Weller; 14.08.2012
comment
@MikeWeller: Ну, в любом случае это стоило поднять, потому что для некоторых случаев использования это гораздо более простой ответ. - person abarnert; 14.08.2012

Простой, но распространенный способ сделать это — проверить список процессов на наличие имени вашего исполняемого файла.

ps - A | grep <your executable name>

person Perception    schedule 30.05.2012

Спасибо @abarnert.

Вот как я сейчас реализовал. В начале main() я бы проверил, существует ли файл с именем .lock в собственном каталоге двоичного файла (я рассматриваю возможность его перемещения в /tmp). Если это так, приложение закрывается.

Если нет, он создаст файл. По окончании приложения файл .lock удаляется

Я еще не записал pid в этот файл, но сделаю это при выходе из предыдущего экземпляра (на данный момент он мне не нужен, но может быть в будущем). Я думаю, что PID можно получить, используя

int myPID=[[NSProcessInfo processInfo] processIdentifier];

Программа будет вызываться настраиваемым планировщиком, работающим как корневой демон. Таким образом, он будет запускаться как root.

Увидев ответы, я бы предположил, что прямого метода решения проблемы нет.

person Renjith    schedule 31.05.2012
comment
Есть две причины написать pid. Во-первых, как вы подразумевали, именно так вы можете убить или иным образом сигнализировать об уже запущенной копии (один из вариантов, которые вы просили в исходном вопросе). Во-вторых, он позволяет отличить заброшенный файл, оставленный аварийным процессом, от живого (например, выполнив уничтожение с сигналом 0). - person abarnert; 31.05.2012
comment
Правильно, нет прямого способа сделать это. Существует множество непрямых методов, но все они сводятся к двум вещам: какому-то объекту с предсказуемым именем (pidfile, сокет, именованный канал, именованный мьютекс ядра NT) или какому-то широковещательному механизму (распределенное уведомление Mac, широковещательное сообщение Windows WM_ и т. д.). Есть библиотеки, которые обобщают детали (см. stackoverflow.com /questions/380870/ для некоторых решений Python, например), хотя единственные C и ObjC, которые я знаю навскидку, являются частью более крупных фреймворков. - person abarnert; 31.05.2012
comment
У меня все еще есть проблемы с концепцией «заблокировать файл». Теперь я думаю об использовании ps -ef | grep ‹argv[0]› | grep -vE 'grep' | awk '{print $2}' , затем захватить вывод и проверить количество строк, чтобы узнать количество экземпляров и их pid. В этом случае я могу убить и другие экземпляры, проверив pid!=myPID. Мне нужно будет проверить, как дать команду через NSTask и поймать ее с помощью NSPipe. Какие-нибудь советы? - person Renjith; 18.06.2012
comment
Разбор ps действительно не очень хороший ответ по ряду причин. Какие проблемы у вас возникают с концепцией файла блокировки? Вы можете взглянуть на сценарии .rc из дистрибутивов Linux, чтобы увидеть, как они обрабатывают детали. - person abarnert; 19.06.2012
comment
У меня есть несколько двоичных файлов, которые запускаются другим двоичным файлом планировщика. Некоторые из двоичных файлов имеют разную функциональность для разных аргументов командной строки. Некоторые из этих двоичных файлов могут выполняться планировщиком одновременно в зависимости от интервала повторения. В этом случае концепция блокировки файла случайным образом дает сбой. У меня есть проверка существования файла блокировки и создание файла блокировки в соседних строках, но если 4 или 5 экземпляров двоичного файла запускаются одновременно, 2 или 3 проходят проверку существования файла блокировки. Какие сценарии .rc, вы имеете в виду сценарии rc.d/init.d или bashrc? - person Renjith; 04.07.2012
comment
Если вы не можете заставить файлы блокировки работать должным образом, вы можете открыть новый вопрос, показав, как именно вы это делаете (обычный минимальный пример рабочего кода), и объяснив, что именно не работает. - person abarnert; 05.07.2012