在某些场景下,例如需要保证单个进程运行,通常的做法是生成一个 PID 文件,并将当前的进程 PID 写入,每次进程启动时检查文件以及进程是否存在。
如果进程异常崩溃没有删除文件,而 Linux 中 PID 可以复用,那么就可能会导致误认为进程存在,虽然概率很低。
其实在 Linux 中可以通过 flock 实现。
在 Linux 中有个简单的实现,也就是 flock() ,这是一个建议性锁,不具备强制性。
也就是说,一个进程使用 flock 将文件锁住,另一个进程仍然可以操作正在被锁的文件,修改文件中的数据,这也就是所谓的建议性锁的内核处理策略。
#include <sys/file.h>
int flock (intfd, int operation);其中 flock() 主要有三种操作类型:
默认的操作是阻塞,可以通过 LOCK_NB 设置为非阻塞。
另外,在命令行中,也可以通过类似如下的方式进行测试。
$ flock -xn /tmp/foobar.lock -c "echo 'Hi world'"常见的如 crontab 。
默认进程在使用 flock 尝试锁文件时,如果文件已经被其它进程锁住,进程会被阻塞直到锁被释放掉。也可以使用 LOCK_NB 参数,此时如果被锁,那么会直接返回错误,对应的 errno 为 EWOULDBLOCK。
简单来说,也就是阻塞、非阻塞两种工作模式。
flock 可以通过 LOCK_UN 显示的释放锁,也可以直接通过关闭 fd 的来释放文件锁,这意味着 flock 会随着进程的关闭而被自动释放掉。
在使用如下测试时,需要保证 /tmp/foobar.txt 存在。
在同一个进程中可以多次进行加锁而不会阻塞,可以通过如下方式进行测试。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
int main(void)
{
int rc, fd;
fd = open("/tmp/foobar.txt", O_RDWR);
printf("current fd: %d\n", fd);
rc = flock(fd, LOCK_EX);
printf("get lock rc: %d\n", rc);
rc = flock(fd, LOCK_EX);
printf("get lock again, rc: %d\n", rc);
sleep(1000);
return 0;
}当启动第二个程序时就会被阻塞掉。
flock 创建的锁是和文件打开表项 struct file 相关联的,而不是文件描述符 fd。
也就是通过 fork() 或者 dup() 复制 fd 后,可以通过这两个 fd 同时操作锁,但是关闭其中一个 fd 锁并不会释放,因为 struct file 并没有释放,只有关闭所有复制出的 fd,锁才会释放。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
int main(void)
{
int rc, fd1, fd2;
fd1 = open("/tmp/foobar.txt", O_RDWR);
fd2 = dup(fd1);
close(fd1);
printf("current fd: %d\n", fd2);
rc = flock(fd2, LOCK_EX);
printf("get lock2, ret: %d\n", rc);
sleep(10);
close(fd2);
printf("release\n");
sleep(10000);
return 0;
}如上,在关闭掉所有的文件描述符之后才会释放掉文件锁。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
int main(void)
{
int rc, pid, fd;
fd = open("/tmp/foobar.txt", O_RDWR);
pid = fork();
if (pid == 0) { /* child */
rc = flock(fd, LOCK_EX);
printf("chile get lock, fd: %d, ret: %d\n", fd, rc);
sleep(10);
printf("child exit\n");
exit(0);
}
rc = flock(fd, LOCK_EX);
printf("parent get lock, fd: %d, ret: %d\n", fd, rc);
sleep(12);
printf("parent exit\n");
return 0;
}子进程持有锁,并不影响父进程通过相同的 fd 获取锁,反之亦然。
当使用 open() 两次打开同一个文件,得到的两个 fd 是独立的,内核会使用两个 struct file 对象,通过其中一个加锁,通过另一个无法解锁,并且在前一个解锁前也无法上锁。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
int main(void)
{
int rc, fd1, fd2;
fd1 = open("/tmp/foobar.txt", O_RDWR);
fd2 = open("/tmp/foobar.txt", O_RDWR);
printf("fd1: %d, fd2: %d\n", fd1, fd2);
rc = flock(fd1, LOCK_EX);
printf("get lock1, ret: %d\n", rc);
close(fd1);
rc = flock(fd2, LOCK_EX);
printf("get lock2, ret: %d\n", rc);
return 0;
}上述代码中,如果注释掉 close(fd1) 会被阻塞。
如果喜欢这里的文章,而且又不差钱的话,欢迎打赏个早餐 ^_^
This Site was built by Rimond, generated with Jekyll, and hosted on GitHub Pages
©2013-2018 – Rimond