LoginSignup
23
22

More than 5 years have passed since last update.

Go言語でプロセス間同期処理

Posted at

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から呼び出します。
代表的なミューテックスを使いました。

lock_windows.go
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を直接呼んでいるのはこちら。

syscall_windows.go
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
}

実際の使い方はこちら

main.go
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しています。

23
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
22