web-dev-qa-db-ja.com

CまたはC ++で単一インスタンスアプリケーションを作成する方法

単一のインスタンスアプリケーションを作成して、一度に1つのプロセスのみを実行できるようにするための提案は何ですか?ファイルロック、ミューテックス、または何?

50
whoi

良い方法は:

_#include <sys/file.h>
#include <errno.h>

int pid_file = open("/var/run/whatever.pid", O_CREAT | O_RDWR, 0666);
int rc = flock(pid_file, LOCK_EX | LOCK_NB);
if(rc) {
    if(EWOULDBLOCK == errno)
        ; // another instance is running
}
else {
    // this is the first instance
}
_

ロックすると、古いpidファイルを無視できることに注意してください(つまり、それらを削除する必要はありません)。何らかの理由でアプリケーションが終了すると、OSがファイルロックを解除します。

Pidファイルは古くなっている可能性があるため、それほど有用ではありません(ファイルは存在しますが、プロセスは存在しません)。したがって、pidファイルを作成およびロックする代わりに、アプリケーション実行可能ファイル自体をロックできます。

より高度な方法は、事前定義されたソケット名を使用してUNIXドメインソケットを作成およびバインドすることです。アプリケーションの最初のインスタンスでバインドが成功します。この場合も、アプリケーションが何らかの理由で終了すると、OSはソケットのバインドを解除します。 bind()が失敗すると、アプリケーションの別のインスタンスがconnect()でき、このソケットを使用してコマンドライン引数を最初のインスタンスに渡すことができます。

59

C++のソリューションを次に示します。マキシムの推奨ソケットを使用します。プロセスがクラッシュし、ロックファイルが削除されない場合、ファイルベースのロックソリューションが失敗するため、ファイルベースのロックソリューションよりもこのソリューションの方が好きです。別のユーザーはファイルを削除してロックすることはできません。プロセスが終了すると、ソケットは自動的に削除されます。

使用法:

int main()
{
   SingletonProcess singleton(5555); // pick a port number to use that is specific to this app
   if (!singleton())
   {
     cerr << "process running already. See " << singleton.GetLockFileName() << endl;
     return 1;
   }
   ... rest of the app
}

コード:

#include <netinet/in.h>

class SingletonProcess
{
public:
    SingletonProcess(uint16_t port0)
            : socket_fd(-1)
              , rc(1)
              , port(port0)
    {
    }

    ~SingletonProcess()
    {
        if (socket_fd != -1)
        {
            close(socket_fd);
        }
    }

    bool operator()()
    {
        if (socket_fd == -1 || rc)
        {
            socket_fd = -1;
            rc = 1;

            if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
            {
                throw std::runtime_error(std::string("Could not create socket: ") +  strerror(errno));
            }
            else
            {
                struct sockaddr_in name;
                name.sin_family = AF_INET;
                name.sin_port = htons (port);
                name.sin_addr.s_addr = htonl (INADDR_ANY);
                rc = bind (socket_fd, (struct sockaddr *) &name, sizeof (name));
            }
        }
        return (socket_fd != -1 && rc == 0);
    }

    std::string GetLockFileName()
    {
        return "port " + std::to_string(port);
    }

private:
    int socket_fd = -1;
    int rc;
    uint16_t port;
};
13
Mark Lakata

ファイルベースのロックを避ける

アプリケーションのシングルトンインスタンスを実装するために、ファイルベースのロックメカニズムを回避することは常に適切です。ユーザーはいつでもロックファイルの名前を別の名前に変更して、次のようにアプリケーションを再実行できます。

mv lockfile.pid lockfile1.pid

どこ lockfile.pidは、アプリケーションを実行する前に存在が確認されるロックファイルです。

そのため、カーネルのみが直接参照できるオブジェクトに対してロックスキームを使用することが常に推奨されます。そのため、ファイルシステムに関係するものは信頼できません。

したがって、最良のオプションはinetソケットにバインドすることです。 UNIXドメインソケットはファイルシステムに存在し、信頼性がないことに注意してください。

または、DBUSを使用して行うこともできます。

5
Binoy

Windowsの場合、名前付きカーネルオブジェクト(たとえば、CreateEvent、CreateMutex)。 UNIXの場合、pidファイル-ファイルを作成し、プロセスIDを書き込みます。

5
Erik

言及されていないようです-共有メモリにミューテックスを作成することは可能ですが、属性によって共有としてマークする必要があります(テストされていません):

pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_t *mutex = shmat(SHARED_MEMORY_ID, NULL, 0);
pthread_mutex_init(mutex, &attr);

共有メモリセマフォもあります(ただし、ロックする方法を見つけることができませんでした)。

int sem_id = semget(SHARED_MEMORY_KEY, 1, 0);
3

「匿名の名前空間」AF_UNIXソケットを作成できます。これは完全にLinux固有ですが、ファイルシステムが実際に存在する必要がないという利点があります。

詳細については、unix(7)のマニュアルページを参照してください。

2
MarkR

アプリケーションに1つのインスタンスとインスタンスを検討するスコープのみを強制することにより、どの問題を回避するかによって異なります。

デーモンの場合—通常の方法は、/var/run/app.pidファイル。

ユーザーアプリケーションについては、実行すべきでないアプリケーションを2回実行できるよりも、アプリケーションを2回実行できないという問題が多くありました。したがって、「理由と範囲」に関する回答は非常に重要であり、おそらく理由と意図された範囲に関する具体的な回答をもたらすでしょう。

2
AProgrammer

誰も言及していませんが、 sem_open() は、現代のPOSIX準拠のOSで実際の名前付きセマフォを作成します。セマフォに初期値1を指定すると、ロックが正常に取得された場合にのみ厳密に解放される限り、それはミューテックスになります。

複数のsem_open()ベースのオブジェクトを使用すると、共通のWindows名前付きオブジェクト(ミューテックス、名前付きセマフォ、および名前付きイベント)をすべて作成できます。 「手動」をtrueに設定した名前付きイベントは、エミュレートするのが少し難しくなります(CreateEvent()SetEvent()、およびResetEvent()を適切にエミュレートするには4つのセマフォオブジェクトが必要です)。とにかく、私は脱線します。

または、名前付き共有メモリがあります。名前付き共有メモリの「共有プロセス」属性でpthreadミューテックスを初期化し、shm_open()/mmap()で共有メモリへのハンドルを開いた後、すべてのプロセスがそのミューテックスオブジェクトに安全にアクセスできます。 sem_open()は、使用しているプラ​​ットフォームで使用できる場合は簡単です(使用できない場合は、健全性のために使用する必要があります)。

使用する方法に関係なく、アプリケーションの単一インスタンスをテストするには、待機関数のtrylock()バリアントを使用します(例 sem_trywait() )。プロセスが実行されている唯一のプロセスである場合、ミューテックスは正常にロックされます。そうでない場合、すぐに失敗します。

アプリケーションの終了時にミューテックスをロック解除して閉じることを忘れないでください。

2
CubicleSoft

書いたばかりで、テストしました。

#define PID_FILE "/tmp/pidfile"
static void create_pidfile(void) {
    int fd = open(PID_FILE, O_RDWR | O_CREAT | O_EXCL, 0);

    close(fd);
}

int main(void) {
    int fd = open(PID_FILE, O_RDONLY);
    if (fd > 0) {
        close(fd);
        return 0;
    }

    // make sure only one instance is running
    create_pidfile();
}
0
Akagi201

これはsem_openに基づいたソリューションです

/*
*compile with :
*gcc single.c -o single -pthread
*/

/*
 * run multiple instance on 'single', and check the behavior
 */
#include <stdio.h>
#include <fcntl.h>    
#include <sys/stat.h>        
#include <semaphore.h>
#include <unistd.h>
#include <errno.h>

#define SEM_NAME "/mysem_911"

int main()
{
  sem_t *sem;
  int rc; 

  sem = sem_open(SEM_NAME, O_CREAT, S_IRWXU, 1); 
  if(sem==SEM_FAILED){
    printf("sem_open: failed errno:%d\n", errno);
  }

  rc=sem_trywait(sem);

  if(rc == 0){ 
    printf("Obtained lock !!!\n");
    sleep(10);
    //sem_post(sem);
    sem_unlink(SEM_NAME);
  }else{
    printf("Lock not obtained\n");
  }
}

別の答えに関するコメントの1つに、「sem_open()がかなり不足していることがわかりました」とあります。不足しているものの詳細についてはわかりません

0
Anil Kainikara

別のスレッドでこのコードを実行するだけです:

void lock() {
  while(1) {
    ofstream closer("myapplock.locker", ios::trunc);
    closer << "locked";
    closer.close();
  }
}

これをメインコードとして実行します。

int main() {
  ifstream reader("myapplock.locker");
  string s;
  reader >> s;
  if (s != "locked") {
  //your code
  }
  return 0;
}
0
ndrewxie

maxim's answer のヒントに基づいて、デュアルロールデーモン(つまり、デーモンとして、およびそのデーモンと通信するクライアントとして機能できる単一のアプリケーション)のPOSIXソリューションを紹介します。このスキームには、最初に起動されたインスタンスがデーモンであり、後続のすべての実行がそのデーモンでの作業をロードオフする場合に、問題のエレガントなソリューションを提供するという利点があります。これは完全な例ですが、実際のデーモンが行うべき多くの機能がありません(たとえば、ロギングに syslogを使用し、バックグラウンドに自分自身を置くforkを使用 、権限をドロップしますなど)が、それはすでにかなり長く、完全にそのまま機能しています。これはこれまでLinuxでしかテストしていませんが、IIRCはすべてPOSIX互換である必要があります。

この例では、クライアントは、最初のコマンドライン引数として渡された整数を、atoiによって解析されたソケットを介して、stdoutに出力するデーモンに送信できます。この種のソケットを使用すると、配列、構造体、さらにはファイル記述子を転送することもできます(man 7 unix)。

#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/un.h>

#define SOCKET_NAME "/tmp/exampled"

static int socket_fd = -1;
static bool isdaemon = false;
static bool run = true;

/* returns
 *   -1 on errors
 *    0 on successful server bindings
 *   1 on successful client connects
 */
int singleton_connect(const char *name) {
    int len, tmpd;
    struct sockaddr_un addr = {0};

    if ((tmpd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
        printf("Could not create socket: '%s'.\n", strerror(errno));
        return -1;
    }

    /* fill in socket address structure */
    addr.Sun_family = AF_UNIX;
    strcpy(addr.Sun_path, name);
    len = offsetof(struct sockaddr_un, Sun_path) + strlen(name);

    int ret;
    unsigned int retries = 1;
    do {
        /* bind the name to the descriptor */
        ret = bind(tmpd, (struct sockaddr *)&addr, len);
        /* if this succeeds there was no daemon before */
        if (ret == 0) {
            socket_fd = tmpd;
            isdaemon = true;
            return 0;
        } else {
            if (errno == EADDRINUSE) {
                ret = connect(tmpd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un));
                if (ret != 0) {
                    if (errno == ECONNREFUSED) {
                        printf("Could not connect to socket - assuming daemon died.\n");
                        unlink(name);
                        continue;
                    }
                    printf("Could not connect to socket: '%s'.\n", strerror(errno));
                    continue;
                }
                printf("Daemon is already running.\n");
                socket_fd = tmpd;
                return 1;
            }
            printf("Could not bind to socket: '%s'.\n", strerror(errno));
            continue;
        }
    } while (retries-- > 0);

    printf("Could neither connect to an existing daemon nor become one.\n");
    close(tmpd);
    return -1;
}

static void cleanup(void) {
    if (socket_fd >= 0) {
        if (isdaemon) {
            if (unlink(SOCKET_NAME) < 0)
                printf("Could not remove FIFO.\n");
        } else
            close(socket_fd);
    }
}

static void handler(int sig) {
    run = false;
}

int main(int argc, char **argv) {
    switch (singleton_connect(SOCKET_NAME)) {
        case 0: { /* Daemon */

            struct sigaction sa;
            sa.sa_handler = &handler;
            sigemptyset(&sa.sa_mask);
            if (sigaction(SIGINT, &sa, NULL) != 0 || sigaction(SIGQUIT, &sa, NULL) != 0 || sigaction(SIGTERM, &sa, NULL) != 0) {
                printf("Could not set up signal handlers!\n");
                cleanup();
                return EXIT_FAILURE;
            }

            struct msghdr msg = {0};
            struct iovec iovec;
            int client_arg;
            iovec.iov_base = &client_arg;
            iovec.iov_len = sizeof(client_arg);
            msg.msg_iov = &iovec;
            msg.msg_iovlen = 1;

            while (run) {
                int ret = recvmsg(socket_fd, &msg, MSG_DONTWAIT);
                if (ret != sizeof(client_arg)) {
                    if (errno != EAGAIN && errno != EWOULDBLOCK) {
                        printf("Error while accessing socket: %s\n", strerror(errno));
                        exit(1);
                    }
                    printf("No further client_args in socket.\n");
                } else {
                    printf("received client_arg=%d\n", client_arg);
                }

                /* do daemon stuff */
                sleep(1);
            }
            printf("Dropped out of daemon loop. Shutting down.\n");
            cleanup();
            return EXIT_FAILURE;
        }
        case 1: { /* Client */
            if (argc < 2) {
                printf("Usage: %s <int>\n", argv[0]);
                return EXIT_FAILURE;
            }
            struct iovec iovec;
            struct msghdr msg = {0};
            int client_arg = atoi(argv[1]);
            iovec.iov_base = &client_arg;
            iovec.iov_len = sizeof(client_arg);
            msg.msg_iov = &iovec;
            msg.msg_iovlen = 1;
            int ret = sendmsg(socket_fd, &msg, 0);
            if (ret != sizeof(client_arg)) {
                if (ret < 0)
                    printf("Could not send device address to daemon: '%s'!\n", strerror(errno));
                else
                    printf("Could not send device address to daemon completely!\n");
                cleanup();
                return EXIT_FAILURE;
            }
            printf("Sent client_arg (%d) to daemon.\n", client_arg);
            break;
        }
        default:
            cleanup();
            return EXIT_FAILURE;
    }

    cleanup();
    return EXIT_SUCCESS;
}
0
stefanct