摘自:http://blog.chinaunix.net/space.php?uid=20196318;http://blog.csdn.net/magictong/article/details/3603015 對于很多服務(wù)來說,在同一個服務(wù)器上只能運行一個實例,那么通過什么方法來保證程序同一時刻只有一個實例運行呢?通過編寫shell腳本來管理程序的啟動、停止是個不錯的方法。在啟動時,shell腳本會創(chuàng)建進程標識文件(存儲正在運行實例的pid)以表明已經(jīng)有實例在運行,如果文件已存在,則說明已有實例在運行,不需要做任何事;在退出時,shell腳本會刪除進程標識文件,表明沒有實例運行。 shell腳本管理方法在應(yīng)用程序之上再包了一層,那么能不能直接在程序開始運行時自己判斷是否有實例在運行呢,答案是肯定的。原理其實差不多,還是要借助公用資源---文件,當然不僅僅是文件而已,還需要文件鎖的支持。大致思路是這樣的:程序在開始運行時對特定文件進行加鎖(不存在則創(chuàng)建),如果加鎖成功,則實例開始運行;如鎖已經(jīng)被占有,則說明已經(jīng)有實例在運行,則程序直接退出;另外在實例運行完畢后對文件的鎖也隨著丟掉了。這樣就能保證每次只有一個程序?qū)嵗谶\行。 具體步驟如下: 1. 打開特定文件(如/var/run/mydaemon.pid),如不存在則創(chuàng)建之; 2. 使用fcntl對文件整個區(qū)域加勸告鎖。 3. 如果加鎖成功,則繼續(xù)執(zhí)行后續(xù)代碼,并將pid寫入文件;如加鎖不成功,說明已經(jīng)有實例在運行,直接退出。 實現(xiàn)示例: #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> #define LOCKFILE "/var/run/mydaemon.pid" #define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) /* set advisory lock on file */ int lockfile(int fd) { struct flock fl;
fl.l_type = F_WRLCK; /* write lock */ fl.l_start = 0; fl.l_whence = SEEK_SET; fl.l_len = 0; //lock the whole file
return(fcntl(fd, F_SETLK, &fl)); } int already_running(const char *filename) { int fd; char buf[16];
fd = open(filename, O_RDWR | O_CREAT, LOCKMODE); if (fd < 0) { printf(LOG_ERR, "can't open %s: %m\n", filename); exit(1); }
/* 先獲取文件鎖 */ if (lockfile(fd) == -1) { if (errno == EACCES || errno == EAGAIN) { printf(LOG_ERR, "file: %s already locked", filename); close(fd); return 1; } printf(LOG_ERR, "can't lock %s: %m\n", filename); exit(1); } /* 寫入運行實例的pid */ ftruncate(fd, 0); sprintf(buf, "%ld", (long)getpid()); write(fd, buf, strlen(buf) + 1); return 0; } int main(int argc, char *argv[]) { if (already_running(LOCKFILE)) return 0; /* 在這里添加工作代碼 */ printf("start main...\n"); sleep(100); printf("main done!\n"); exit(0); } 綜述:讓一個程序只運行一個實例的方法有多種,但是原理都類似,也就是在程序創(chuàng)建前,有窗口的程序在窗口創(chuàng)建前,檢查系統(tǒng)中是否已經(jīng)設(shè)置了某些特定標志了,如果有說明已經(jīng)有一個實例在運行了,則當前程序通知用戶怎樣怎樣,然后程序退出,當然方法有這么多,各自也就有自己的優(yōu)缺點了。<注意下面的程序都是分塊拷貝的> 方法一: return nRet; // 在創(chuàng)建窗口前調(diào)用下面代碼 break; case -1:// 無法創(chuàng)建,退出 方法二: 方法三: 這種方法相比上面兩種方法,避免上面兩種方法的缺點,通過SetProp()為程序主窗口設(shè)置一個特殊的Property,然后在啟動時遍歷所有的窗口,找出包含著個Property的窗口局柄 。【這個附加的窗口屬性在窗口銷毀時也應(yīng)該銷毀】這個方法的缺點就是代碼比較多一點,如下: // 聲明全局的 屬性 名和 屬性值 // 定義枚舉窗口回調(diào)函數(shù) // 主窗口創(chuàng)建前判斷 ::ShowWindow(oldHWnd, SW_NORMAL); // 顯示 // 主窗口創(chuàng)建后設(shè)置,為窗口附加一個屬性 // 主窗口退出時移除該附加屬性 方法四: 上面的方法二和方法三都有一個弊病,不知道大家發(fā)現(xiàn)沒,那就是依賴于窗口的存在,沒有窗口的程序怎么辦了,用方法一是可以的,不過方法一不太適合即時修改狀態(tài),譬如我想提供選項給用戶,可以即時修改是否允許多實例,像KMP就提供了即時修改是否允許多實例,使用全局變量是一個比較好的解決方案,使用全局共享變量的方法則主要是在VC框架程序中通過編譯器來實現(xiàn)的。通過#pragma data_seg預(yù)編譯指令創(chuàng)建一個新節(jié),在此節(jié)中可用volatile關(guān)鍵字定義一個變量,而且必須對其進行初始化。Volatile關(guān)鍵字指定了變量可以為外部進程訪問。最后,為了使該變量能夠在進程互斥過程中發(fā)揮作用,還要將其設(shè)置為共享變量,同時允許具有讀、寫訪問權(quán)限。這可以通過#pragma comment預(yù)編譯指令來通知編譯器。下面給出使用了全局變量的進程互斥代碼清單: #pragma data_seg("Shared") if (0 == g_lAppInstance) 【注意,代碼應(yīng)該放在程序的入口處】 其實上面的方法可以兩種進行組合來實現(xiàn)一些比較特殊的需求,具體怎樣就自己去想了。 |
|