文件加锁
对文件加锁是为了避免,多个进程或线程这种并发情况下,对同一个文件进行修改时造成的混乱
比如说进程A修改了文件的内容,进程B也修改了文件的内容,并且覆盖了A写的内容,然么进程A读取的内容就和预想的不同了,从而造成了混乱
对文件加锁就和信号量这些同步技术类似,只不过它是专门为文件设计的
对文件加锁的 一般步骤:
-
给文件加锁
-
执行文件I/O
- 在执行I/O时,当文件锁与
stdio
混合使用时,需要注意 缓冲区 和 加锁解锁的时机
比如说(假设使用stdio库),当进程A对文件加锁,写入数据,数据被存储在缓冲区中,然后解锁,此时数据并不一定会刷新到文件中,然后进程B执行同样的步骤,这就导致了,写入顺序的混乱
为了避免这种情况,一定要注意缓冲区的情况:
- 用
read
或write
代替stdio
库 - 在对文件加锁之后立即刷新stdio流,并且在释放锁之前立即再次刷新这个流
setbuf
禁用缓冲区
- 在执行I/O时,当文件锁与
-
解锁文件使得其他进程能够给文件加锁
对整个文件加锁
flock()系统调用在整个文件上放置一个锁。待加锁的文件是通过传入 fd 的一个打开着的文件描述符来指定的。在默认情况下,如果另一个进程已经持有了文件上的一个不兼容的锁,那么 flock()会阻塞。
int flock(fd, int operation);
任意数量的进程可同时持有一个文件上的共享锁,但在同一个时刻只有一个进程能够持有一个文件上的互斥锁。(换句话说,互斥锁会拒绝其他进程的互斥和共享锁请求)
以下是多个进程对同一个文件加锁的兼容性
flock加锁是对 文件本身 作用的,也就是说,如果对指向同一个文件的不同的文件描述符加锁解锁,都只是对最终的文件进行操作
尽管flock对整个文件加锁,是很方便,但这还是影响了并发性能,也许两个进程虽然对同一个文件操作,但他们可能并不会影响对方,在这种情况下,仍然对整个文件加锁,就降低了并发性能
对文件区域加锁
使用fcntl()能够在一个文件的任意部分上放置一把锁,这个文件部分既可以是一个字节,也可以是整个文件。这种形式的文件加锁通常被称为记录加锁,但这种称谓是不恰当的,因为 UNIX 系统上的文件是一个字节序列,并不存在记录边界的概念,文件记录的概念只存在于应用程序中。
上图展示了使用记录锁(对文件区域加锁)对进程的同步,当进程请求访问被锁住的区域时会阻塞
fcntl(fd, cmd, &flockstr);
-
参数:
fd
: 待加锁文件的文件描述符flockstr
:定义了待获取或删除锁,也就是对锁的描述
l_type
表示锁的类型---读锁、写锁、删除锁,l_whence
表示文件指针的位置,l_start
表示距离文件指针的偏移位置,l_len
表示锁住区域的长度cmd
F_SETLK
: 非阻塞方式设置锁,非阻塞就是如果锁不兼容则会立即失败返回,而不是阻塞等待解锁F_SETLKW
: 阻塞方式设置锁F_GETLK
:检测是否能够获取flockstr指定的区域上的锁,但实际不获取这把锁
死锁
当两个进程锁需要访问的区域被对方锁住时,即形成一个循环时,发生死锁
锁的饿死和排队加锁请求的优先级
排队的锁请求被准予的顺序是不确定的。如果多个进程正在等待加锁,那么它们被满足的顺序取决于进程的调度。
写者并不比读者拥有更高的优先权,反之亦然。
flock和fcntl对比
锁定范围
-
flock:
flock 锁是文件级别的锁,当一个进程对文件加锁时,锁住的是 整个文件 -
fcntl: 锁定文件的部分区域。可以在 文件的不同部分 设置不同的锁
锁定方式
-
flock:
flock 提供两种锁类型:- 独占锁:
LOCK_EX
- 共享锁:
LOCK_SH
支持解锁:
LOCK_UN
- 独占锁:
-
fcntl:
fcntl 提供三种锁类型:
- 读锁:
F_RDLCK
- 写锁:
F_WRLCK
- 解锁:
F_UNLCK
- 读锁:
锁的持有者
flock和fcntl都是对 文件本身(即inode项) 进行加锁,但是 flock 认为锁的持有者是 打开文件句柄(即系统文件表项),而 fcntl 认为锁的持有者是 进程(即进程文件描述符表项)
这种区别造成的影响:
-
锁的作用范围:
-
flock
: 所有指向同一个文件的文件描述符(无论是来自同一进程,还是不同进程)都会共享同一个锁 -
fcntl
: 每个进程持有的文件描述符的锁状态独立于其他进程或线程。换句话说,进程可以对文件的不同区域加锁,并且这些锁是 进程局部 的,在其他进程的文件描述符中不可见
-
-
锁的继承:
-
flock
: 由于 flock 是 基于系统文件表项 的,因此当一个进程通过 open() 打开一个文件并加锁时,所有指向该文件的其他文件描述符(无论是由同一进程还是其他进程打开的)都会共享该锁。换句话说,flock 锁是继承的,不同的文件描述符共享同一把锁 -
fcntl
: fcntl 锁是 基于文件描述符 的,因此只有对 特定进程 的文件描述符上的锁进行修改。其他进程在其自己的文件描述符上操作时,fcntl 锁对它们没有直接影响。fcntl 锁不被继承,每个进程的锁是独立的
-
-
锁的释放
-
flock
:当进程关闭文件时,锁会自动释放。也就是说,如果一个进程用flock
锁住了文件并关闭了文件,锁会立即释放,其他进程无法再通过不同的文件描述符获取锁 -
fcntl
:锁与进程的文件描述符绑定,不会自动释放,只有当进程关闭文件描述符,或者显式调用fcntl
来解除锁时,锁才会释放。因此,fcntl
锁可能在进程运行期间持续存在,直到显式解除
-
文件关闭和文件描述符关闭不同,文件描述符关闭,可能它所指代的文件仍然保持打开状态,即 文件描述符关闭是关闭对文件的引用,而文件关闭是指文件资源的完全释放
劝告式加锁和强制式加锁
“劝告式加锁”和“强制式加锁”这两个术语通常出现在讨论并发编程、资源竞争和锁机制的场景中。它们描述了系统如何在多进程或多线程环境下对共享资源进行访问控制和管理。
1. 劝告式加锁(Advisory Locking)
特性 | 劝告式加锁(Advisory Locking) | 强制式加锁(Mandatory Locking) |
---|---|---|
锁的强制性 | 非强制,其他进程可以选择是否遵守锁 | 强制,其他进程必须遵守锁 |
阻塞行为 | 不阻塞,进程可以自由选择是否遵守 | 会阻塞尝试访问锁的进程 |
适用场景 | 不需要严格同步的应用,容忍某些竞争条件 | 需要强同步的场景,确保数据一致性 |
典型应用 | 文件系统的 flock() ,数据库中的某些实现 |
文件系统的 fcntl() ,数据库锁 |
优点 | 灵活,减少锁的冲突,易于实现 | 数据一致性高,适用于关键任务 |
缺点 | 可能出现资源竞争,导致数据损坏 | 性能可能下降,容易死锁 |