1. 尾部加锁与解锁
在记录锁尾部加锁解锁容易产生大坑。这很隐秘,有必要提一提。
比如下面的代码:
wlock(fd, 0, SEEK_END, 0);
// 追加一字节
write(fd, buf, 1);
unlock(fd, 0, SEEK_END, 0);
// 再次追加一字节
write(fd, buf, 1);
看起来似乎很完美的一段程序,其实是有 bug 的。它运行后的结果是这样的:
图1 尾部加锁与解锁后的结果
图 1 中的结果显然不是我们预期的,对于第一次追加完成后,明明解锁了啊?其实不然,当你解锁的时候,此时 SEEK_END 的位置已经变了不是?
2. 实验
程序 lockendfile 对文件 end.txt 加锁后,追加一字节数据,然后解锁。同样这里使用 testlock 程序进行测试。
2.1 代码
// lockendfile.c
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#define PERR(err, msg) do { errno = err; perror(msg); exit(-1); } while(0)
int lockend(int fd) {
puts("locking...");
struct flock flk;
int err;
flk.l_type = F_WRLCK;
flk.l_start = 0;
flk.l_whence = SEEK_END;
flk.l_len = 0;
err = fcntl(fd, F_SETLKW, &flk);
if (err < 0) PERR(errno, "lock");
puts("locked...");
return err;
}
int unlockend(int fd) {
puts("unlocking...");
struct flock flk;
int err;
flk.l_type = F_UNLCK;
flk.l_start = 0;
flk.l_whence = SEEK_END;
flk.l_len = 0;
err = fcntl(fd, F_SETLKW, &flk);
if (err < 0) PERR(errno, "unlock");
puts("unlocked...");
return err;
}
int main() {
printf("pid: %d\n", getpid());
int fd = open("end.txt", O_WRONLY| O_APPEND);
lockend(fd);
write(fd, "l", 1);
sleep(5);
unlockend(fd);
sleep(30);
close(fd);
return 0;
}
2.2 编译和运行
- 编译
$ gcc lockendfile.c -o lockendfile
- 运行
图1 运行结果
图 1 中左侧程序在 locked 后,向 end.txt 文件追加了一字节数据,在解锁前,使用 testlock 进行测试,发现被加锁。这没毛病。
然后在 unlocked 之后,程序未结束前,再一次 testlock,发现仍然是被加锁的状态,说明这一次 unlocked 并未对那追加的一字节成功解锁。
程序结束后,testlock 测试发现已经没有锁了。
3. 总结
- 知道尾部加锁可能会产生的问题