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

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

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

```go: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
}
```

実際の使い方はこちら

```go: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しています。

https://github.com/unirita/cuto