- 单例进程的理解
有些程序在设备上只允许运行1个,机运行第二个时会自动判断已有进程是否运行,如果有则直接退出。那么这些进程是如何做到的?答案有很多,下面列举一下我看到过的方法
1.linux下使用 ps -ef|grep 进程名,Windows下使用tasklist | findstr 进程名,这样可以找到是否已有进程在运行
这种方案无疑是最简单的,但是存在一个问题,程序如果改名了怎么办?
2.进程运行时创建一个文件 tmp,当进程退出后删除该文件,如果第二次运行该进程则检测tmp文件是否存在
这种方案也有一个弊端,及进程异常退出tmp文件没有被删除咋办?
3.另外就是为文件加锁,进程运行时一直锁定文件,退出或者异常退出则释放文件锁
这种方案无疑是最可靠的,也是很多项目正在使用的方案,具体实现看下面
4.进程启动连接localhost:xxxx端口,如果有进程监听该端口则说明已有进程运行,没有则本次运行监听
该方案最省事,但是必须占用本机一个端口,程序退出或异常退出时会取消监听端口
- 下面介绍go语言的文件锁实现单例运行
package main
import (
"fmt"
"io/ioutil"
"os"
"syscall"
)
/**
1. 文件已被某个进程锁住则说明已有进程运行
2. 如果没有进程锁定则写入当前进程pid到文件
3. pid文件如果不存在则会主动创建一个
4. 返回:true => 已有进程运行,false => 没有进程运行
**/
func SingleProcess(sigfile string) bool {
// 打开一个文件,如果文件不存在则创建
f, err := os.OpenFile(sigfile, os.O_RDONLY|os.O_CREATE, os.ModePerm)
if err != nil {
return true
}
// 下面是为文件加锁
if syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB) != nil {
return true
}
// 锁住的文件关闭会释放锁
//if err = f.Close(); err != nil {
// return err
//}
// 下面是写入当前进程的pid到文件
if ioutil.WriteFile(sigfile, []byte(fmt.Sprintln(os.Getpid())), os.ModePerm) != nil {
return true
}
return false
}
- 下面介绍监听本地端口方式实现单例进程
/**
5. 当尝试连接失败时,会启动协程一直监听端口
6. 这样再次运行该进程时,就能成功连接该端口
7. 返回true -> 已有进程运行,false -> 没有进程运行
**/
func SingleProcess(port int) bool {
// 得到本地监听地址和端口
address := fmt.Sprintf("localhost:%d", port)
// 尝试连接服务器
conn, err := net.Dial("tcp", address)
if err != nil {
go func() { /* 连接不成功,启用服务 */
l, err := net.Listen("tcp", address)
if err != nil {
return
}
defer l.Close()
for {
l.Accept()
}
}()
// 连接失败,说明没有进程运行
return false
}
defer conn.Close()
// 连接成功说明已有进程监听
return true
}
- 以上就是我的两种方案,个人比较倾向于文件锁,但是这种方法仅适用于linux,在Windows下还是老老实实用占用端口的方案吧。
最后由
Janbar 修改于
2017-09-19 19:26