被遗忘的桃源——flock 文件锁

flock是Linux系统中用于处理多进程间文件读写冲突的工具,尤其在后台开发和crontab任务中。它提供共享锁和排他锁,以非强制性方式确保进程间的文件访问同步。flock不仅能在加锁失败时选择阻塞或非阻塞模式,还能在进程复制、fork和execve中保持文件锁的正确性。了解flock的工作原理和使用技巧,能有效避免数据异常和资源耗尽的问题。
摘要由CSDN通过智能技术生成

缘起

在后台开发中,随着多线程应用日益增多,人们对多进程的关注也在逐渐减弱。但是在实际应用中,多进程之间的通信及资源共享,还是会不时的遇到,如果不认真处理,就会出现数据异常,甚至导致系统资源耗尽的情形。今天,我在这里为大家介绍一下 flock 文件锁——一个处理多个进程间文件共享冲突、解决crontab 中单任务多实例问题的利器。

在开发过程中,经常会出现这样一种情况:服务依赖的数据变动不是特别频繁,例如天或周级别的更新。这类数据通常是格式化后存储在数据文件中,即以数据文件的形式进行数据更新。

文件的更新的基本流程如下:

这里写图片描述

可以看出,对同一个文件,有两个不同的进程在分别进行写和读,这就出现了多进程下对文件的读写冲突问题。在这种场景下的多进程读写文件冲突,与常规的多进程读写冲突是有区别的。主要区别的点在于,常规的多进程是同一服务中通过 fork 派生出来的多进程,读写冲突通常是对内存中的数据结构进行读写时发生的。而数据文件更新场景下的多进程,产生冲突的操作对象是文件,而且这里的进程可能是两个独立的、无任何关系的进程,例如写进程是python 脚本,而读进程是一个 C++ 服务。由于文件读写进程的独立性,对这种读写冲突的处理需要操作系统层面的支持。

文件锁 flock

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

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

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

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

共享锁与排他锁

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

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

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

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

/* 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 排他锁时,同一时刻只能有一个进程锁定成功,其余进行只能阻塞(阻塞与非阻塞在下文介绍),这种行为与多线程读写锁中的写锁类似。

以两个进程为例,假设进程1先对文件加了锁,而后进程2又尝试对同一文件加锁,这时进程2的行为如下表所示:

进程1 (先加锁) 进程2(后加锁) 进程2表现
LOCK_SH LOCK_SH 不阻塞
LOCK_SH LOCK_EX 阻塞
LOCK_EX LOCK_SH 阻塞
LOCK_EX LOCK_EX 阻塞

可见,只有两个进程都对文件加的都是共享锁时,进程可以正常执行,不会阻塞,其他情形下,后加锁的进程都会被阻塞。

在 flock 的man page 中,关于文件锁的互斥有如下描述:

A call to flock() may block if an incompatible lock is held by another process.

这里所说的不相容的锁就是指会导致阻塞的两个锁,上面表格中后三行中的锁,都是两两不相容的,只有第一行的两个共享锁是相容的。

阻塞与非阻塞

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

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

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

在非阻塞模式下&#

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值