go语言中判断一个文件是否被flock上了

首先需要知道的是linux存在强制锁(mandatory lock)和劝告锁(advisory lock)。劝告锁是一种协同工作的锁。对于这种锁来说,内核只提供加锁以及检测文件是否已经加锁的手段,但是内核并不参与锁的控制和协调,所以只是个约定,对文件打个标签的感觉,具体遵守不遵守看自己;强制锁是一种内核强制采用的文件锁,每当有系统调用 open()、read() 以及write() 发生的时候,内核都要检查并确保这些系统调用不会违反在所访问文件上加的强制锁约束。也就是说,如果有进程不遵守游戏规则,硬要往加了锁的文件中写入内容,内核就会加以阻拦。更多参考这里

我这里利用文件锁来做等待的事情,即前面有一个进程跑着呢,后面这个干同样事情的进程就得等着,并且前一个进程无论是正常退出还是异常退出的时候锁必须释放掉

看了下,用flock加LOCK_EX排它锁或者用fnctl加F_SETLKW也可以,其实都是相当于对文件加个标记(注意这句话)

flock可以直接用系统命令测试 一个终端开touch lockfile && date && flock lockfile sleep 5 另外一个终端开flock lockfile date ,另外终端这个flock会卡五秒钟后再输出结果

fnctl可以用以下代码测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
#include <fcntl.h>
#include <sys/stat.h>

int main() {
int fd = open("lockfile" ,O_RDWR);
struct flock lock;
lock.l_type = F_WRLCK;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
lock.l_pid = getpid();
int rd = fcntl(fd,F_SETLKW,&lock);
printf("%d\n",rd);
sleep(10);
printf("%d\n",rd);
return 0;
}

或者是go代码 可以从https://golang.org/pkg/syscall/ 这里看到些提示

setlkw.go
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
import (
"fmt"
"syscall"
"time"

)

func main() {
fd,err:= syscall.Open("lockfile",syscall.O_WRONLY,0644)
fmt.Println(err)
fstat := syscall.Flock_t{
Type: syscall.F_WRLCK,
Whence:0,
}
fgetlk := syscall.Flock_t{}
//if err := syscall.Flock(fd, syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
// if err == syscall.EWOULDBLOCK {
// fmt.Println("blocked")
// }
// fmt.Println(err)
//}
fmt.Println("test")
err = syscall.FcntlFlock(uintptr(fd),syscall.F_SETLKW,&fstat)
fmt.Printf("%#v",fstat)
fmt.Println(err,"\n")
time.Sleep(10 * time.Second)
fmt.Printf("%#v",fgetlk)
err = syscall.FcntlFlock(uintptr(fd),syscall.F_GETLK,&fgetlk)
fmt.Printf("%#v",fgetlk)
fmt.Println(err,"\n")


}

另外一个文件来check 锁,注意得是2个进程,一个进程内会认为无锁

getlk.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import (
"fmt"
"syscall"
)

func main() {
fd,err:= syscall.Open("lockfile",syscall.O_WRONLY,0644)
fmt.Println(err)
fgetlk := syscall.Flock_t{}
err = syscall.FcntlFlock(uintptr(fd),syscall.F_GETLK,&fgetlk)
//这里应该是 F_WRLCK = 0x1
fmt.Printf("%#v",fgetlk)
fmt.Println(err,"\n")
}

另外一个有意思的是如果用flock加锁一个文件,但是用fcntl getlk来查看这个锁,会得到
syscall.Flock_t{Type:2, Whence:0, Pad_cgo_0:[4]uint8{0x0, 0x0, 0x0, 0x0}, Start:0, Len:0, Pid:0, Pad_cgo_1:[4]uint8{0x0, 0x0, 0x0, 0x0}}
一开始以为是F_UNLCK = 0x2 没有锁上后来才发现其实是LOCK_EX = 0x2,所以其实都是个标记。。

更多参考

http://blog.jobbole.com/104331/
https://www.ibm.com/developerworks/cn/linux/l-cn-filelock/
https://gavv.github.io/blog/file-locks/
https://www.kancloud.cn/kancloud/understanding-linux-processes/52169
https://golang.org/src/syscall/syscall_unix_test.go