进程单例运行,文件加锁
前言
很多时候我们都需要进程单例运行,当再次运行程序时检查到已有程序在运行可以做特别的操作,比如置顶已运行的程序,比如当前程序提示一下就退出。
最简单方案是打开进程创建一个文件,程序结束时删除文件,当第二个程序运行时判断该文件存在则认为已有程序运行。问题是程序异常退出没有删除那个文件就GG了。
还有方案就是进程启动时锁住一个文件,进程退出释放锁,进程异常退出由系统自动释放锁。这个就是完美的方案。
还有一种方案就是判断进程名是否允许,Linux下可以执行【ps auxf | grep xxx】,Windows下可以执行【tasklist | findstr xxx】。问题就是程序文件名被改就GG。
另外还可以监听一个tcp端口,如果第二个进程运行也监听相同端口会报错,因此也能实现进程单例运行,只是需要占用一个端口。
在win平台下还可以通过创建一个互斥体实现进程单例。
flock命令
命令介绍
该工具是用来获取一个文件锁,并执行命令。当文件已经被锁则不会执行命令,使用该命令可有效防止重复执行一些操作。特别是cron脚本,由于脚本执行周期长,下一次定时执行又到了,可以不重复执行。
Windows下也是可以安装flock命令的,我是安装【msys2】后默认就有了。下面看下该工具的帮助文档:
copyflock -h
Usage:
flock [options] <file>|<directory> <command> [<argument>...]
flock [options] <file>|<directory> -c <command>
flock [options] <file descriptor number>
Manage file locks from shell scripts.
Options:
-s, --shared get a shared lock // 获取读锁(共享锁),多个进程可获取并读文件,其他进程获取写锁会返回失败
-x, --exclusive get an exclusive lock (default) // 获取写锁(排它锁),只允许一个进程获取,其他进程获取均返回失败
-u, --unlock remove a lock
-n, --nonblock fail rather than wait // 当获取锁失败时直接返回,不带此选项程序会卡住,直到获取到锁
-w, --timeout <secs> wait for a limited amount of time // 获取锁超时的时间
-E, --conflict-exit-code <number> exit code after conflict or timeout // 当获取锁失败时,返回的错误码.在Linux执行【echo $?】,win执行【echo %errorlevel%】查看
-o, --close close file descriptor before running command
-c, --command <command> run a single command string through the shell // 锁住文件的同时执行一个命令,我一般用来执行一个脚本
--verbose increase verbosity
-h, --help display this help and exit
-V, --version output version information and exit
For more details see flock(1).
flock实例
代码实现
获取文件锁
可以执行
go get github.com/jan-bar/golibs
,下载我写好的库,关于Windows和Linux下获取文件锁和释放文件锁。
下面的代码,同时执行2个进程会返回错误file is lock
,请看示例程序:
copypackage main
import (
"os"
"strconv"
"time"
"github.com/jan-bar/golibs/filelock"
)
func main() {
fr, err := filelock.LockOpenFile("a.txt", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666, filelock.WriteLock)
if err != nil {
panic(err)
}
defer fr.Close()
fr.File.Write([]byte(strconv.Itoa(os.Getpid())))
time.Sleep(time.Second * 5)
}
文件锁判断单例
可以根据返回错误值判断文件是否被锁住,如果锁住就认为已有进程在执行。
copypackage main
import (
"fmt"
"github.com/jan-bar/golibs"
)
func main() {
err := golibs.SingletonFile("a.txt")
if err == golibs.ErrSingleton {
fmt.Println("已有进程在运行")
}
}
tcp端口判断单例
如果本机指定端口已经被监听,则证明已有程序在运行。
copypackage main
import (
"fmt"
"github.com/jan-bar/golibs"
)
func main() {
err := golibs.SingletonTcp(8080)
if err == golibs.ErrSingleton {
fmt.Println("已有进程在运行")
}
}
创建互斥体实现单例
原理是使用win32api中的
CreateMutexW
创建一个互斥体,多个进程都创建同名互斥体时如果已有进程先创建后面的进程会提示已存在。
copypackage main
import (
"fmt"
"github.com/jan-bar/golibs"
)
func main() {
err := golibs.SingletonWin("process")
fmt.Println(err)
fmt.Scanln()
}
总结
进程单例运行在很多场景都是有必要的,上面介绍的几种方案,我比较喜欢通过加锁文件来判断进程是否已经被运行过,因为只占用一个文件。很多工具都是通过锁文件,并将自己的pid
写入文件,方便其他操作去读取这个进程的pid,而不用通过ps去查询,堪称完美。我只测试了Windows和Linux下的文件锁功能,Windows是使用win32api去实现,Linux是系统自带的接口。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· PostgreSQL 和 SQL Server 在统计信息维护中的关键差异
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~