现实情况是你所开发的程序有可能被意外地多次拉起,而使用文件锁的排他锁功能可以解决这个问题。

文件锁有几个好处:

  1. 避免多个进程同时存在
  2. 程序意外中断,文件锁会自动解锁,而不自己亲自去收拾残局

使用起来也很简单,只需要指定一个lock文件,然后使用syscall.Flock()去上锁和解锁即可。

简单的,可以像这样子使用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
func main() {
   // 打开文件锁
   lock, e := os.Create(LockFile)
   if e != nil {
      log.Printf("main: 创建文件锁失败: %s", e)
      os.Exit(ExitStatusLockCreateError)
   }
   defer os.Remove(LockFile)
   defer lock.Close()

   // 尝试独占文件锁
   e = syscall.Flock(int(lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
   if e != nil {
      log.Printf("main: 独占文件锁失败: %s", e)
      os.Exit(ExitStatusLockExError)
   }
   defer syscall.Flock(int(lock.Fd()), syscall.LOCK_UN)

   // your code ...
}

很多时候,像上边这样子就可以了, 如果觉得自己想封装一下,也是OK的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package flock

import (
	"errors"
	"os"
	"syscall"
)

type Flock struct {
	LockFile string
	lock     *os.File
}

// 创建文件锁,配合 defer f.Release() 来使用
func Create(file string) (f *Flock, e error) {
	if file == "" {
		e = errors.New("cannot create flock on empty path")
		return
	}
	lock, e := os.Create(file)
	if e != nil {
		return
	}
	return &Flock{
		LockFile: file,
		lock:     lock,
	}, nil
}

// 释放文件锁
func (f *Flock) Release() {
	if f != nil && f.lock != nil {
		f.lock.Close()
		os.Remove(f.LockFile)
	}
}

// 上锁,配合 defer f.Unlock() 来使用
func (f *Flock) Lock() (e error) {
	if f == nil {
		e = errors.New("cannot use lock on a nil flock")
		return
	}
	return syscall.Flock(int(f.lock.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
}

// 解锁
func (f *Flock) Unlock() {
	if f != nil {
		syscall.Flock(int(f.lock.Fd()), syscall.LOCK_UN)
	}
}

封装了之后,在main()函数上的调用可以像这样子:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
	// 打开文件锁
	lock, e := flock.Create(LockFile)
	if e != nil {
		// handle error
	}
	defer lock.Release()

	// 尝试独占文件锁
	e = lock.Lock()
	if e != nil {
		// handle error
	}
	defer lock.Unlock()
	// your code ...
}