Golang如何实现单进程实例

Golang如何实现单进程实例 大家好, 我对Go语言非常陌生,目前正在寻找一种方法来确保我的程序以单实例方式运行,也就是说,如果程序已经在运行,用户就不能再启动一个新的实例。可以想象一下你的浏览器、大多数大型应用程序以及移动操作系统上的任何应用程序。 起初,我寻找一种依赖操作系统锁的API,类似于Qt系统信号量所提供的方式(https://doc.qt.io/qt-5/qsystemsemaphore.html),但没有真正找到任何可靠的方案。 我想知道在Go语言中,是否还有其他更受青睐的跨平台方式来实现这一目标,而无需重新发明轮子。此外,我不想依赖文件系统作为进程间通信的手段。 非常感谢大家的意见和建议! D


更多关于Golang如何实现单进程实例的实战教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

不确定是否有更推荐的方法,但你可以尝试使用这个包 - https://github.com/marcsauter/single

更多关于Golang如何实现单进程实例的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


感谢Kris。您的解决方案具有简洁的优点,但它是否容易产生竞态条件/误报?我觉得任何基于文件系统的解决方案都存在可证明的缺陷,即使在设计良好的文件系统上也是如此。

您可以采用许多Linux程序(可能还有其他操作系统)已经使用的方法。在启动时,只需将您的进程ID(pid)写入一个特定文件,然后在启动时可以读取该文件并检查进程是否仍在运行。这样您就能知道是否已有实例在运行。请记住,在写入自己的pid之前先读取该文件。

kync:

lock := fslock.New(“…/pid.lock”)

这看起来仍然是基于文件系统的,但比 GitHub - marcsauter/single: Golang package to ensure, that only one instance of a program is running 质量高得多,谢谢!

我也考虑过那种方法,但遗憾的是在我的情况下不可行,因为我们无法承担开放端口的代价。然而,它的行为完全符合预期,而操作系统级的IPC原语,如系统信号量和共享内存,在Unix/Linux系统上无法用于此目的,因为这些系统在进程崩溃时不会清理/恢复进程所使用的资源。注意:这些原语在Windows上的行为符合预期。

flock 或许能帮到你。

package main

import (
	"fmt"
	"github.com/juju/fslock"
	"time"
)

func main() {
	lock := fslock.New("../pid.lock")
	err := lock.TryLock()
	if err != nil {
		fmt.Println(err.Error())
		return
	}

	defer lock.Unlock()
}

我有一个非常琐碎(且有趣)的想法,就是在你的应用程序的某个地方,在一个 goroutine 中运行一个什么都不做的虚拟服务器,如下例所示。我没有尝试太多,但我猜这在任何操作系统上都应该有效,因为你无法在同一个端口上运行两次服务器。 sunglasses

package main

import "net/http"

func main() {
   if http.ListenAndServe(":65535", nil) != nil {
      println("Already run!")
      return
   }
}

在Go语言中,实现单进程实例的跨平台方案,推荐使用文件锁(flock)或命名互斥锁(named mutex)。以下是两种方法的示例代码:

1. 使用文件锁(跨平台,推荐)

package main

import (
    "fmt"
    "os"
    "syscall"
)

func main() {
    lockFile := "/tmp/myapp.lock"
    file, err := os.OpenFile(lockFile, os.O_CREATE|os.O_RDWR, 0666)
    if err != nil {
        panic(err)
    }
    defer file.Close()

    err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
    if err != nil {
        fmt.Println("Another instance is already running")
        os.Exit(1)
    }
    defer syscall.Flock(int(file.Fd()), syscall.LOCK_UN)

    // 主程序逻辑
    fmt.Println("Application started")
    select {}
}

2. Windows专用命名互斥锁

// +build windows

package main

import (
    "fmt"
    "syscall"
    "time"
)

func main() {
    mutexName := "Global\\MyAppMutex"
    mutex, err := syscall.CreateMutex(nil, false, &mutexName)
    if err != nil {
        panic(err)
    }
    defer syscall.CloseHandle(mutex)

    result := syscall.WaitForSingleObject(mutex, 0)
    if result == syscall.WAIT_OBJECT_0 {
        fmt.Println("Application started")
        time.Sleep(10 * time.Second) // 模拟程序运行
    } else {
        fmt.Println("Another instance is already running")
    }
}

3. 使用第三方库(推荐)

安装单实例库:

go get github.com/gofrs/flock

示例代码:

package main

import (
    "fmt"
    "log"
    "time"
    "github.com/gofrs/flock"
)

func main() {
    lock := flock.New("/tmp/myapp.lock")
    locked, err := lock.TryLock()
    if err != nil {
        log.Fatal(err)
    }
    
    if !locked {
        fmt.Println("Another instance is already running")
        return
    }
    defer lock.Unlock()

    // 主程序逻辑
    fmt.Println("Application started")
    time.Sleep(30 * time.Second)
}

4. 跨平台完整实现

package main

import (
    "fmt"
    "log"
    "os"
    "runtime"
    "time"
    
    "github.com/gofrs/flock"
)

func isAlreadyRunning() bool {
    var lockPath string
    if runtime.GOOS == "windows" {
        lockPath = os.Getenv("TEMP") + "\\myapp.lock"
    } else {
        lockPath = "/tmp/myapp.lock"
    }
    
    fileLock := flock.New(lockPath)
    locked, err := fileLock.TryLock()
    if err != nil {
        log.Fatal(err)
    }
    
    if !locked {
        return true
    }
    
    // 程序退出时自动释放锁
    go func() {
        for {
            select {
            case <-time.After(time.Second):
                if fileLock.Locked() {
                    continue
                }
                return
            }
        }
    }()
    
    return false
}

func main() {
    if isAlreadyRunning() {
        fmt.Println("Application is already running")
        os.Exit(1)
    }
    
    fmt.Println("Application started successfully")
    // 主程序逻辑
    time.Sleep(60 * time.Second)
}

这些方法中,使用github.com/gofrs/flock库是最简洁的跨平台方案,它封装了不同操作系统的底层文件锁实现。文件锁在进程退出时会自动释放,避免了死锁问题。

回到顶部