Goにはプロセス間の同期がない?
goルーチンという素晴らしいスレッド(厳密には違うようですが)の仕組みを提供してくれているGolangですが、プロセス間の制御をする仕組みがありませんでした。
複数のプロセスから、1つのファイルにログを記録したいと考えていました。
「golangにファイルロックの仕組みはないのですか?」という質問へのAndrew Gerrandの回答は「syscall.Flockを使え」というもの。
もちろんこれはWindowsでは使えない。(コンパイルエラーになってしまう)
(回答は2011年のものだが、今も事情は変わっていない様子)
https://groups.google.com/forum/#!topic/golang-nuts/L6KKPCxysXw
LinuxやMacだけならばこれでいいのですが、Windowsにも対応したい。Javaではなく、Goを使うのだから、せめてビジネスロジックだけでも違うソースにはしたくない。。。
というわけで、作ってみました。
Windowsでのプロセス間同期
WindowsAPIをGolangから呼び出します。
代表的なミューテックスを使いました。
package util
import (
"errors"
"fmt"
"os"
"syscall"
"unsafe"
)
// ミューテックスハンドルを保持する。
type LockHandle struct {
handle uintptr
isLock bool
}
const (
wAIT_OBJECT_0 int = 0
wAIT_ABANDONED int = 128
wAIT_TIMEOUT int = 258
)
var ErrBusy = errors.New("Locked by other process.")
// プロセス間で共通に使用する名前を指定する。
func InitLock(name string) (*LockHandle, error) {
mutexName := fmt.Sprintf("Global\\%s", name)
hMutex, _, err := procCreateMutexW.Call(
0, 0, uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(mutexName))))
if hMutex == 0 {
fmt.Fprintf(os.Stderr, "Failed InitMutexW() err = %v", err)
return nil, err
}
return &LockHandle{hMutex, false}, nil
}
// ロックを開始する。
// 引数でタイムアウト時間(ミリ秒)を指定する。
func (l *LockHandle) Lock(timeout_milisec int) error {
r1, _, err := procWaitForSingleObject.Call(l.handle, uintptr(timeout_milisec))
if int(r1) == wAIT_OBJECT_0 || int(r1) == wAIT_ABANDONED {
// Lock成功
l.isLock = true
return nil
} else if int(r1) == wAIT_TIMEOUT {
msg := fmt.Sprintf("Lock Timeout. Detail( %v )", err)
fmt.Fprintf(os.Stderr, "%v\n", msg)
return ErrBusy
}
return fmt.Errorf("Lock Unknown Error. Detail( %v )", err)
}
// ロック中であれば、解除する。
func (l *LockHandle) Unlock() error {
if l.isLock {
r1, _, err := procReleaseMutex.Call(l.handle)
if int(r1) == 0 { // 失敗
return fmt.Errorf("Unlock Error. Detail( %v )", err)
}
l.isLock = false
return nil
}
return nil
}
// InitMutexで確保したミューテックスオブジェクトを破棄する。
func (l *LockHandle) TermLock() error {
procCloseHandle.Call(l.handle)
return nil
}
WindowsAPIを直接呼んでいるのはこちら。
package util
import (
"syscall"
)
// DLLハンドル
type goDll struct {
dll *syscall.DLL
}
var (
kernel32_dll = loadDLL("kernel32.dll")
procCreateMutexW = kernel32_dll.findProc("CreateMutexW")
procWaitForSingleObject = kernel32_dll.findProc("WaitForSingleObject")
procReleaseMutex = kernel32_dll.findProc("ReleaseMutex")
procCloseHandle = kernel32_dll.findProc("CloseHandle")
)
func loadDLL(name string) *goDll {
dll, err := syscall.LoadDLL(name)
if err != nil {
panic(err)
}
goDll := new(goDll)
goDll.dll = dll
return goDll
}
func (c *goDll) findProc(name string) *syscall.Proc {
proc, err := c.dll.FindProc(name)
if err != nil {
panic(err)
}
return proc
}
実際の使い方はこちら
func main() {
locker, lockErr := util.InitLock(lockName)
if lockErr != nil {
return lockErr
}
defer locker.TermLock()
//lockが必要な処理を開始
locker.Lock(10)
//lockが不要になった
locker.Unlock()
}
なんとなく形になったかな?
Linux側が「lock_linux.go」を同じインタフェースで作っておけば、使い回せそう。
ここで作ったライブラリは、GithubにもUpしています。