Linux应用程序的一个常见需求是从一个文件中读取一些数据,修改这些数据,然后将这些数据写回文件。只要在一个时刻只有一个进程以这种方式使用文件就不会存在问题,但当多个进程同时更新一个文件时问题就出现了(会导致文件的内容不是按照我们的预期进行存储)。
- 为防止多进程同一时刻操作同一文件出现问题,就需要采用某种形式的进程间同步。
- 通常我们会对文件进行加锁处理,因为内核能够自动将锁与文件关联起来。
在Linux中,文件加锁是通过使用文件锁(File Locks)来实现的。文件锁主要有两种类型:共享锁(Shared Lock)和排他锁(Exclusive Lock)。这些锁用于控制对文件的并发访问,以防止多个进程同时对同一文件进行读或写操作,从而保护文件的一致性。
文件锁的类型
1. 共享锁(读锁 - Shared Lock)
- 多个进程可以同时持有共享锁。
- 一个持有共享锁的进程可以阻止其他进程获取排他锁。
- 使用
F_RDLCK
或LOCK_SH
表示。
2. 排他锁(写锁 - Exclusive Lock)
- 只能由一个进程持有。
- 如果一个进程持有排他锁,其他进程无法获取共享锁或排他锁。
- 使用
F_WRLCK
或LOCK_EX
表示。
3. 解锁
- 使用
F_UNLCK
或LOCK_UN
表示,用于释放已经获取的锁。
阻塞和非阻塞锁定
1. 阻塞锁定
- 进程尝试获取锁时,如果锁已被其他进程占用,该进程将被阻塞直到锁可用。
2. 非阻塞锁定
- 进程尝试获取锁时,如果锁已被其他进程占用,该进程不会被阻塞,而是立即返回失败。
文件锁的作用域
1. 进程间锁
- 锁定适用于同一文件的不同进程。
2. 线程间锁
- 锁定适用于同一进程的不同线程。
文件锁的持续性
1. 进程关闭时解锁
- 锁定将在持有锁定的进程终止时自动释放。
2. 文件关闭时解锁
- 锁定将在持有锁定的文件被关闭时自动释放。
在 Linux 中,文件锁是通过系统调用 fcntl
或者 flock
来实现的。
使用 fcntl
进行文件锁定
1. 锁定整个文件
#include <fcntl.h>
struct flock fl;
fl.l_type = F_WRLCK; // 或 F_RDLCK 代表读锁
fl.l_whence = SEEK_SET;
fl.l_start = 0;
fl.l_len = 0; // 锁定整个文件
fcntl(fd, F_SETLKW, &fl); // F_SETLKW 表示阻塞方式设置锁
2. 解锁整个文件
fl.l_type = F_UNLCK;
fcntl(fd, F_SETLK, &fl);
3. 锁定文件的一部分
fl.l_type = F_WRLCK; // 或 F_RDLCK 代表读锁
fl.l_whence = SEEK_SET;
fl.l_start = 100; // 从文件的偏移量 100 处开始锁定
fl.l_len = 50; // 锁定 50 个字节
fcntl(fd, F_SETLKW, &fl);
使用 flock
进行文件锁定
1. 锁定整个文件
#include <fcntl.h>
flock(fd, LOCK_EX); // LOCK_EX 代表排他锁
2. 解锁整个文件
flock(fd, LOCK_UN);
3. 锁定文件的一部分
flock
不支持锁定文件的一部分。
注意事项
- 使用
fcntl
可以实现更复杂的锁定策略,例如非阻塞锁定、记录锁定等。 fcntl
锁定是进程级别的,不同进程的文件锁互不影响;而flock
锁定是进程组级别的,一个进程组中的锁定会影响到同一进程组的其他进程。- 文件锁对 NFS 文件系统的支持因实现而异,可能有一些限制。
文件锁是多进程或多线程环境下对文件进行同步的一种有效方式,可以防止多个进程同时修改同一文件导致的问题。在实际应用中,根据具体需求和环境选择适合的文件锁定方式。