本文将mark下flock 文件锁相关notes。本文内容主要转载自被遗忘的桃源——flock 文件锁

文件锁 flock

为解决多进程对同一文件的读写冲突,在linux 系统中,提供了 flock 这一系统调用,用来实现对文件的读写保护,即文件锁的功能。文件锁保护文件的功能,与pthread 库中多线程使用读写锁来保护内存资源的方式是类似的。 flock 的 man page 中有如下介绍:

flock - apply or remove an advisory lock on an open file

从中可以解读出两点内容:

  • flock 提供的文件锁是建议性质的。所谓 “建议性锁”,通常也叫作非强制性锁,即一个进程可以忽略其他进程加的锁,直接对目标文件进行读写操作。因而,只有当前进程主动调用 flock去检测是否已有其他进程对目标文件加了锁,文件锁才会在多进程的同步中起到作用。表述的更明确一点,就是如果其他进程已经用 flock 对某个文件加了锁,当前进程在读写这一文件时,未使用 flock 加锁(即未检测是否已有其他进程锁定文件),那么当前进程可以直接操作这一文件,其他进程加的文件锁对当前进程的操作不会有任何影响。这种可以被忽略、需要双方互相检测确认的加锁机制,就被称为 ”建议性“ 锁
  • 文件锁必须作用在一个打开的文件上,即从应用的角度看,文件锁应当作用于一个打开的文件句柄上。

共享锁与互斥锁

linux 中 flock 系统调用的原型如下:

1
2
#include <sys/file.h>
int flock(int fd, int operation);

当 flock 执行成功时,会返回0;当出现错误时,会返回 -1,并设置相应的 errno 值。

在flock 原型中,参数 operation 可以使用 LOCK_SH 或 LOCK_EX 常量,分别对应共享锁和排他锁。这两个常量的定义在 file.h 中。与 flock 相关的常量定义如下:

1
2
3
4
5
6
7
/* Operations for the `flock' call.  */                                          
#define LOCK_SH 1 /* Shared lock. */
#define LOCK_EX 2 /* Exclusive lock. */
#define LOCK_UN 8 /* Unlock. */

/* Can be OR'd in to one of the above. */
#define LOCK_NB 4 /* Don't block when locking. */

当使用 LOCK_SH 共享锁时,多个进程都可以使用共享锁锁定同一个文件,从而实现多个进程对文件的并行读取。由此可见,LOCK_SH 共享锁类似于多线程读写锁 pthread_rwlock_t 类型中的读锁。当使用LOCK_EX 排他锁时,同一时刻只能有一个进程锁定成功,其余进行只能阻塞,这种行为与多线程读写锁中的写锁类似。

阻塞与非阻塞

flock 文件锁提供了阻塞和非阻塞两种使用方式。当处于阻塞模式时,如果当前进程无法成功获取到文件锁,那么进程就会一直阻塞等待,直到其他进程在对应文件上释放了锁,本进程能成功持有锁为止。在默认情况下,flock 提供是阻塞模式的文件锁。

在日常使用中,文件锁还会使用在另外一种场景下,即进程首先尝试对文件加锁,当加锁失败时,不希望进程阻塞,而是希望 flock 返回错误信息,进程进行错误处理后,继续进行下面的处理。在这种情形下就需要使用 flock 的非阻塞模式。把flock 的工作模式设置为非阻塞模式非常简单,只要将原有的 operation 参数改为锁的类型与 LOCK_NB 常量进行按位或操作即可,例如:

1
2
int ret = flock(open_fd, LOCK_SH | LOCK_NB);
int ret = flock(open_fd, LOCK_EX | LOCK_NB);

在非阻塞模式下,加文件锁失败并不影响进程流程的执行,但要注意加入错误处理逻辑,在加锁失败时,不能对目标文件进行操作。

flock 命令

除了多种语言提供 flock 系统调用或函数,linux shell 中也提供了 flock 命令。
flock(1)


参考资料:

  1. 被遗忘的桃源——flock 文件锁
  2. flock(1)
  3. flock(2)