1. 程式人生 > >linux下程式如何實現單例項執行

linux下程式如何實現單例項執行

1、技術原理

無論是windows還是linux下,程式設計者都會遇到一個問題,那就是如何實現程式的單例項執行。比如,Windows自帶的播放軟體Windows Medea Player只能啟動一個例項。原因很簡單,如果同時啟動幾個例項,卻播放不同的檔案,那麼聲音和影象就會引起混亂。所以,我們要做的就是,程式啟動時檢測一下系統中是否已經存在一個完全一樣的例項,如果已經存在,則本次啟動的程式自動退出。那麼,從編碼的角度來分析,該如何實現這個效果呢?在linux系統中,我們一般有以下幾種方法:

  1. shell使用ps命令來判斷。最常見也是最簡單的方案,直接寫一個特定的shell指令碼來實現。不過存在可移植性差的問題,程序名字一旦改變之後,指令碼就失去作用無法監控。同時,還要額外附加這個指令碼檔案在系統中執行,實在有點多餘;
  2. 訊號量/共享記憶體。使用共享記憶體,通過shmget操作共享記憶體,然後寫入pid,這樣就不用生成可見的檔案。這個方案只存在一個很小的缺陷,需要配置共享記憶體的key,並保證不與系統其他應用衝突,一般來說,衝突概率非常小;
  3. 埠搶佔。應用於大多數的Linux網路應用。思路是系統保證每個埠的TCP只能有一個程序監聽,那麼如果程式啟動時,監聽一個核心的埠,第二個執行的例項就會監聽失敗,無法啟動。這個方案同樣很有效,省去了一個額外的配置檔案,不足之處是一般只用於帶網路的程式;
  4. 建立檔案,加鎖(建議性鎖)。這種做法最常見了,應用於大多數的Linux程式,如apache httpd, mysql。思路是配置一個pid檔案,當程式啟動時,對pid檔案加鎖,然後寫入本程序的pid,如果鎖失敗,說明有例項已經啟動了。這個方案非常可靠,唯一的不足是需要配置一個pid檔案,並且保證檔案目錄和檔案可寫;
  5. 程序列表檢測。對於運維常用的方法。由於運維不一定能控制程式的修改,所以考慮從外部解決。crontab指令碼,查詢執行的程序數,一旦發現程序數與預期不符,那麼killall,重啟程序。這個方案是旁路方案,比上面的方式更通用,還可以監視程序數,避免某些子程序core。這個方案沒有什麼缺陷,如果硬要找一個的話,不同的系統ps命令輸出可能不一樣,指令碼需要考慮移植。

2、原始碼實現

經過一番對比,優選其中的方案4,也是最大眾化的一個方案。下面就是詳細的程式碼實現:

  • single_instance.c原始碼
/**************************************************************************************************
**                                                                                               **
**  檔名稱:  single_instance.c                                                                 **
**  版權所有:  CopyRight @ LEON WorkStudio CO.LTD. 2017                                          **
**  檔案描述:  判斷系統中是否已經存在該程序的例項                                                **
**  ===========================================================================================  **
**  建立資訊:  | 2017-9-14 | LEON | 建立本模組                                                   **
**  ===========================================================================================  **
**  修改資訊:  單擊此處新增....                                                                  **
**************************************************************************************************/
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <printf.h> #include <string.h> #include <errno.h> #include <sys/stat.h> /************************************************************************************************** ** 函式名稱: lockfile ** 功能描述: 對檔案加鎖 ** 輸入引數: 無 ** 輸出引數: 無 ** 返回引數: 無 **************************************************************************************************/ static int lockfile(int fd) { struct flock fl; fl.l_type = F_WRLCK; fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; return(fcntl(fd, F_SETLK, &fl)); } /************************************************************************************************** ** 函式名稱: proc_is_exist ** 功能描述: 判斷系統中是否存在該程序 ** 輸入引數: procname: 程序名 ** 輸出引數: 無 ** 返回引數: 返回1表示系統中已經存在該程序了;返回0表示系統中不存在該程序 ** 注意事項: 此處加鎖完後無需對檔案進行close,而是程序退出後由系統來釋放;否則無法起到保護的作用 **************************************************************************************************/ int proc_is_exist(const char *procname) { int fd; char buf[16]; char filename[100]; sprintf(filename, "/var/run/%s.pid", procname); fd = open(filename, O_RDWR | O_CREAT, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)); if (fd < 0) { printf("open file \"%s\" failed!!!\n", filename); return 1; } if (lockfile(fd) == -1) { /* 嘗試對檔案進行加鎖 */ printf("file \"%s\" locked. proc already exit!!!\n", filename); close(fd); return 1; } else { ftruncate(fd, 0); /* 寫入執行例項的pid */ sprintf(buf, "%ld", (long)getpid()); write(fd, buf, strlen(buf) + 1); return 0; } }
  • single_instance.h原始碼
/**************************************************************************************************
**                                                                                               **
**  檔名稱:  single_instance.h                                                                 **
**  版權所有:  CopyRight @ LEON WorkStudio CO.LTD. 2017                                          **
**  檔案描述:  判斷系統中是否已經存在該程序的例項                                                **
**  ===========================================================================================  **
**  建立資訊:  | 2017-9-14 | LEON | 建立本模組                                                   **
**  ===========================================================================================  **
**  修改資訊:  單擊此處新增....                                                                  **
**************************************************************************************************/
#ifndef SINGLE_INSTANCE_H
#define SINGLE_INSTANCE_H

#ifdef __cplusplus
extern "C"
{
#endif

int proc_is_exist(const char *procname);

#ifdef __cplusplus
}
#endif

#endif

3、使用說明

呼叫者只需在程式啟動時呼叫本函式,根據返回值進行判斷即可:

if (proc_is_exist(g_fk_process_name) == TRUE) {                             /* 單例項執行 */
    print_sys("an \"%s\" already running in system. exit now...\n", g_fk_process_name);
    return 0;
} else {
    print_sys("\"%s\" starting...\n", g_fk_process_name);
}