限制使用Golang的单个可执行文件实例

huangapple go评论148阅读模式
英文:

Restricting to Single Instance of Executable with Golang

问题

我需要一次只允许一个实例的 Golang 可执行文件。我不确定如何使用全局互斥锁来确保没有其他实例在运行。

这将在 Windows 机器上运行。

英文:

I need to only allow one instance of my Golang executable at a time. I'm not sure how to use a Global Mutex to make sure no other instances are running.

This would be running on a Windows Machine.

答案1

得分: 11

我知道这个话题有点旧,但最近我在Windows上需要它,我会在这里发布我是如何做到的,以防其他人需要。

感谢@VonC指导我正确的方向。

var (
    kernel32        = syscall.NewLazyDLL("kernel32.dll")
    procCreateMutex = kernel32.NewProc("CreateMutexW")
)

func CreateMutex(name string) (uintptr, error) {
    ret, _, err := procCreateMutex.Call(
        0,
        0,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))),
    )
    switch int(err.(syscall.Errno)) {
    case 0:
        return ret, nil
    default:
        return ret, err
    }
}

// mutexName以"Global\"开头将在所有用户会话中起作用
_, err := CreateMutex("SomeMutexName")

我创建了一个包含更完整示例的库:https://github.com/rodolfoag/gow32

谢谢!

英文:

I know this topic is a bit old, but I needed it recently on Windows and I'll post here how I did it in case someone else needs.

Thx to @VonC for pointing me in the right direction.

var (
    kernel32        = syscall.NewLazyDLL("kernel32.dll")
    procCreateMutex = kernel32.NewProc("CreateMutexW")
)

func CreateMutex(name string) (uintptr, error) {
    ret, _, err := procCreateMutex.Call(
        0,
        0,
        uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(name))),
    )
    switch int(err.(syscall.Errno)) {
    case 0:
        return ret, nil
    default:
        return ret, err
    }
}

// mutexName starting with "Global\" will work across all user sessions
_, err := CreateMutex("SomeMutexName")

I created a lib with a more complete example: https://github.com/rodolfoag/gow32

Thx!

答案2

得分: 8

在跨平台解决方案中似乎没有一个(除了编写一个文件,并在启动时查找该文件)。

在Windows上,这个线程报告

推荐的方法(也是我使用的方法)是使用CreateSemaphore函数
如果您指定的名称以“Global\”开头,则信号量对整个系统是唯一的,尝试打开它的第二次尝试将失败。

这是一个kernel32调用,在Go中有一些可用的包装器

kostix在评论中补充道:

查看围绕pkg\syscall层次结构的Go源代码--它包含了如何使用syscalls调用Windows DLL的丰富示例(这是您访问Windows API的方式)。

这将是syscall/dll_windows.go。(这里是一个gist

brainman的odbc包是在Windows上直接调用API的另一个示例--可能更容易理解。

类似于api/zapi_windows.go

英文:

There doesn't seem to be a cross-platform solution (beside writing a file, and looking for that file at start time).

On Windows, this thread reports

> the recommended approach (and the one that has worked great for me) is to use the CreateSemaphore function.
If the name you specify starts with "Global\", then the semaphore is unique for the entire system and a second attempt to open it will fail.

This is a kernel32 call, which has some wrapper in Go available.

kostix adds in the comments:

> look at the Go source code around the pkg\syscall hierarchy -- it contains a good wealth of examples on how to call out to DLLs on Windows using syscalls (and that's how you access anything in Windows API).

That would be syscall/dll_windows.go. (And here is a gist)

> The odbc package by brainman is another example of direct API calls on Windows -- possibly easier to digest.

Like api/zapi_windows.go.

答案3

得分: 6

你可以使用 sockets,它简单易用,并且可以在任何平台上使用。

package main

import (
	"fmt"
	"net"
	"os"
	"strings"
)

const (
	INSTANCE_PORT = 9292
)

func main() {
	listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", INSTANCE_PORT))
	if err != nil {
		if strings.Index(err.Error(), "in use") != -1 {
			// 可选择将命令行参数发送给其他实例
			fmt.Fprintln(os.Stderr, "Already running.")
			return
		} else {
			panic(err)
		}
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			println("Error accept:", err.Error())
			return
		}
		go do_something_with(conn)
	}
}

这是一个使用 sockets 的示例代码,它监听在本地的 9292 端口上。如果端口已经被占用,它会打印 "Already running." 的消息并退出。然后,它会不断地接受连接,并在新的 goroutine 中处理每个连接。你可以在 do_something_with 函数中编写具体的处理逻辑。

英文:

You could use sockets, simple to use and will work on everything really.

package main

import (
	"fmt"
	"net"
	"os"
	"strings"
)

const (
	INSTANCE_PORT = 9292
)

func main() {
	listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", INSTANCE_PORT))
	if err != nil {
		if strings.Index(err.Error(), "in use") != -1 {
			//optionally send command line arguments to the other instance
			fmt.Fprintln(os.Stderr, "Already running.")
			return
		} else {
			panic(err)
		}
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			println("Error accept:", err.Error())
			return
		}
		go do_something_with(conn)
	}
}

答案4

得分: 4

你可以从tendo的Python库中适应这段代码source

他们的做法是:
对于Windows系统:

创建一个由可执行文件的绝对路径组成的文件(因为它是一个库,所以在你的情况下,你可以只定义一个标识符,以防止你把可执行文件放在两个地方)

  • 对于Windows系统:首先尝试删除文件(如果存在),如果不存在,则使用os.O_CREAT | os.O_EXCL | os.O_RDWR创建文件
  • 对于兼容POSIX系统:首先尝试删除文件(如果存在),如果不存在,则创建文件并使用fcntl.LOCK_EX | fcntl.LOCK_NB获取文件锁

任何失败都意味着程序已经在运行

然后你可以使用延迟操作来删除锁(在POSIX系统上)并删除文件

Go允许你通过构建注释来创建两个版本,以告诉编译器根据你的操作系统编译哪个文件,所以你有:

对于Unix系统:

// +build !windows

package main

import (
	"os"
	"syscall"
)

func create_lock_file(filename string) (*os.File, error) {
	file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
	if err != nil {
		return nil, err
	}
	err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
	if err != nil {
		return nil, err
	}
	return file, nil
}

对于Windows系统:

// +build !windows

package main

import (
	"os"
)

func create_lock_file(filename string) (*os.File, error) {
	if _, err := os.Stat(filename); err == nil {
		err = os.Remove(filename)
		if err != nil {
			return nil, err
		}

	}
	return os.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
}

还有一个测试:

package main

import (
	"fmt"
	"time"
)

func main() {

	_, err := create_lock_file("plop.lock")
	if err != nil {
		fmt.Println("error ", err.Error())
	}

	time.Sleep(10 * time.Second)
	fmt.Println("end ")
}

我已经开始了一个基于此的库你可以在这里找到

英文:

You could adapt the code from tendo's python library source

what they do is
for windows :

creating a file made of the executable absolute path (well it's a library, so in your case, you can just define an identifier, to prevent you from "i put the executable in 2 places")

  • For windows: trying first to remove the file if existing, and if not creating the file with os.O_CREAT | os.O_EXCL | os.O_RDWR
  • For POSIX compatible systems: trying first to remove the file if existing and if not creating the file and acquiring a lock on it using fcntl.LOCK_EX | fcntl.LOCK_NB

any failure mean the program is already running

and then you can use a defer action to remove the lock (on posix system) and delete the file

Go permit you to create both version wit a build comment to tell which file to compile depending on your OS so you have

for unix system

// +build !windows

package main

import (
	"os"
	"syscall"
)

func create_lock_file(filename string) (*os.File, error) {
	file, err := os.OpenFile(filename, os.O_WRONLY, 0666)
	if err != nil {
		return nil, err
	}
	err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
	if err != nil {
		return nil, err
	}
	return file, nil
}

for windows:

// +build !windows

package main

import (
	"os"
)

func create_lock_file(filename string) (*os.File, error) {
	if _, err := os.Stat(filename); err == nil {
		err = os.Remove(filename)
		if err != nil {
			return nil, err
		}

	}
	return os.OpenFile(filename, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666)
}

and a test

package main

import (
	"fmt"
	"time"
)

func main() {

	_, err := create_lock_file("plop.lock")
	if err != nil {
		fmt.Println("error ", err.Error())
	}

	time.Sleep(10 * time.Second)
	fmt.Println("end ")
}

I've started a library out of it that you can find here

答案5

得分: 0

对这个答案进行了改进(我不确定这个答案是否会扭曲原始含义,所以我写了一个新的答案)。

改进的特点:

  • 已弃用StringToUTF16Ptr已弃用。请改用UTF16PtrFromString
  • 添加了CloseHandle,以便可以取消CreateMutexW
package _test

import (
	"syscall"
	"testing"
	"unsafe"
)

var (
	kernel32         = syscall.NewLazyDLL("kernel32.dll")
	procCreateMutexW = kernel32.NewProc("CreateMutexW")
	procCloseHandle  = kernel32.NewProc("CloseHandle")
)

// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexW#return-value
func CreateMutexW(proc *syscall.LazyProc, name string) (uintptr, error) {
	if proc.Name != "CreateMutexW" {
		panic("proc.Name != CreateMutexW")
	}
	lpName, _ := syscall.UTF16PtrFromString(name) // LPCWSTR
	if handleID, _, err := proc.Call(
		0,
		0,
		uintptr(unsafe.Pointer(lpName)),
	); err.(syscall.Errno) == 0 {
		return handleID, nil
	} else {
		return handleID, err
	}
}

// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle?redirectedfrom=MSDN
func CloseHandle(proc *syscall.LazyProc, handle uintptr) error {
	if proc.Name != "CloseHandle" {
		panic("proc.Name != CloseHandle")
	}
	val, _, err := proc.Call(handle)
	if val == 0 {
		return err
	}
	return nil
}

func TestCreateMutexW(t *testing.T) {
	handle, err := CreateMutexW(procCreateMutexW, "hello world")
	if err != nil {
		t.Fatalf(err.Error())
	}

	_, err = CreateMutexW(procCreateMutexW, "hello world")
	if err == nil || err != syscall.ERROR_ALREADY_EXISTS {
		t.Error("should panic")
	}

	if err = CloseHandle(procCloseHandle, handle); err != nil {
		t.Error(err)
	}

	// We can create again since we have closed.
	handle, _ = CreateMutexW(procCreateMutexW, "hello world")
	if err = CloseHandle(procCloseHandle, handle); err != nil {
		t.Error(err)
	}
}
英文:

Improvements to this answer. (I am unsure if this answer will distort the original meaning, so I have written a new answer.)

Features:

  • deprecated: StringToUTF16Ptr is deprecated. Use UTF16PtrFromString instead.
  • Add the CloseHandle so that you can cancel the CreateMutexW.
package _test

import (
	"syscall"
	"testing"
	"unsafe"
)

var (
	kernel32         = syscall.NewLazyDLL("kernel32.dll")
	procCreateMutexW = kernel32.NewProc("CreateMutexW")
	procCloseHandle  = kernel32.NewProc("CloseHandle")
)

// https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createmutexW#return-value
func CreateMutexW(proc *syscall.LazyProc, name string) (uintptr, error) {
	if proc.Name != "CreateMutexW" {
		panic("proc.Name != CreateMutexW")
	}
	lpName, _ := syscall.UTF16PtrFromString(name) // LPCWSTR
	if handleID, _, err := proc.Call(
		0,
		0,
		uintptr(unsafe.Pointer(lpName)),
	); err.(syscall.Errno) == 0 {
		return handleID, nil
	} else {
		return handleID, err
	}
}

// https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle?redirectedfrom=MSDN
func CloseHandle(proc *syscall.LazyProc, handle uintptr) error {
	if proc.Name != "CloseHandle" {
		panic("proc.Name != CloseHandle")
	}
	val, _, err := proc.Call(handle)
	if val == 0 {
		return err
	}
	return nil
}

func TestCreateMutexW(t *testing.T) {
	handle, err := CreateMutexW(procCreateMutexW, "hello world")
	if err != nil {
		t.Fatalf(err.Error())
	}

	_, err = CreateMutexW(procCreateMutexW, "hello world")
	if err == nil || err != syscall.ERROR_ALREADY_EXISTS {
		t.Error("should panic")
	}

	if err = CloseHandle(procCloseHandle, handle); err != nil {
		t.Error(err)
	}

	// We can create again since we have closed.
	handle, _ = CreateMutexW(procCreateMutexW, "hello world")
	if err = CloseHandle(procCloseHandle, handle); err != nil {
		t.Error(err)
	}
}

huangapple
  • 本文由 发表于 2014年4月19日 05:53:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/23162986.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定