Skip to content

Commit 24e3a67

Browse files
committed
Add file_lock article.
1 parent 05bd9d0 commit 24e3a67

File tree

1 file changed

+89
-0
lines changed

1 file changed

+89
-0
lines changed

file_lock.md

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
---
2+
title: Unix 系统文件锁踩坑记
3+
date: 2024-04-27 13:00
4+
categories: 文件
5+
---
6+
7+
# Unix 系统文件锁踩坑记
8+
## 1、背景
9+
10+
最近的一个项目需要通过文件锁方式实现多进程之间的资源互斥,但却遇到一个诡异的现象:当进程数比较少时,感觉加锁是正常的,但当进程数一多,加锁就失效了,似乎多个进程都可以同时加一把锁,锁的互斥性完全失效。因为锁功能的实现经过Acl库(https://github.com/acl-dev/acl )进行了二次封装,同时项目本身又比较复杂,所以决定做一个简单的例子测试一下原因。
11+
12+
## 2、问题分析
13+
14+
一般来讲,编写简单示例来复现线上系统问题并不是一件容易的事,毕竟线上系统模块众多而且复杂,有时很难知道哪些模块会导致问题发生。为了复现线上问题,专门写了一个简单的demo(参见:https://github.com/acl-dev/demo/blob/master/c/lock/file_lock.c ),通过对比测试不同条件加锁行为最终复现了线上系统问题,并最终得以解决。
15+
16+
在 Unix 平台上对文件进行加锁时一般会使用 fcntl 系统API,示例代码如下:
17+
18+
```c
19+
int fcntl_lock(const char *filename) {
20+
int fd = open(filename, O_RDWR | O_CREAT, 0600);
21+
struct flock lock;
22+
23+
if (fd == -1) {
24+
return -1;
25+
}
26+
27+
lock.l_type = F_WRLCK;
28+
lock.l_whence = SEEK_SET;
29+
lock.l_start = 0;
30+
lock.l_len = 0;
31+
32+
int ret = fcntl(fd, F_SETLKW, &lock);
33+
...
34+
}
35+
```
36+
37+
单独测试这段代码是没有问题的:当第一个进程使用上述方法对指定文件加锁后,其它进程无法采用同样的方式再次加锁。
38+
39+
但在加锁成功后,如果再次打开该文件然后关闭,则就发生了预料之外的问题,针对上述代码稍作修改:
40+
41+
```c
42+
...
43+
int ret = fcntl(fd, F_SETLKW, &lock);
44+
if (ret == -1) {
45+
return -1;
46+
}
47+
48+
int fd2 = open(filename, O_RDONLY, 0600);
49+
if (fd2 >= 0) {
50+
close(fd2);
51+
}
52+
...
53+
```
54+
55+
对修改后(对同一文件进行第二次打开并`关闭`)的加锁代码进行测试时发现`加锁互斥`作用失效(即多个进程均可以对同一文件进行加锁);另外,如果仅有二次打开并没有关闭,则文件锁的互斥行为依然有效,看来问题出在第二次打开文件并`关闭`后(有可能在关闭后内核的某些行为使 `fcntl` 加锁文件失效)。
56+
57+
编译上面 file_lock.c 源码生成 file_lock 可执行程序,然后以 `./file_lock -o -c` 方式启动两个进程,会发现两个进程均可以正常加锁,表明文件加锁失效。
58+
59+
## 3、问题解决
60+
61+
既然使用 `fcntl` API 加锁文件存在以上缺陷,则需要采用其它方法避免这一问题。Unix 系统提供了 `flock` API 对文件进行加锁互斥。使用 flock 加锁文件的示例如下:
62+
63+
```c
64+
int flock_lock(const char *filename) {
65+
int fd = open(filename, O_RDWR | O_CREAT, 0600);
66+
if (fd == -1) {
67+
return -1;
68+
}
69+
70+
int ret = flock(fd, LOCK_EX);
71+
if (ret == -1) {
72+
return -1;
73+
}
74+
75+
int fd2 = open(filename, O_RDONLY, 0600);
76+
if (fd2 >= 0) {}
77+
close(fd2);
78+
}
79+
...
80+
}
81+
```
82+
83+
通过测试发现,在调用 `flock` 后再对同一文件进行『打开、关闭』操作,文件锁依然是有效的(即只有第一个进程可以加锁成功,后续进程无法再次加锁)。看来 `flock` 解决了 `fcntl` 对文件加锁时遇到的问题。
84+
85+
为了避免用户在使用文件锁时遇到类似的坑,在 Acl 中对文件锁进行了封装,并在内部优先使用 `flock` 方式加锁。可以参考示例:https://github.com/acl-dev/demo/blob/master/c%2B%2B/file/file_lock.cpp 。
86+
87+
## 4、小结
88+
89+
看似一个简单的小问题却耗费了1,2天时间进行分析,这进一步加深了自己对文件锁的理解。另外一方面,知识经验是需要长期积累的,与智商无关,因为有些知识点本就是一层窗户纸:会就是会,不会就是不会。

0 commit comments

Comments
 (0)