在多个进程同时操做同一份文件的过程当中,很容易致使文件中的数据混乱,须要锁操做来保证数据的完整性,这里介绍的针对文件的锁,称之为“文件锁”-flock。
flock,建议性锁,不具有强制性。一个进程使用flock将文件锁住,另外一个进程能够直接操做正在被锁的文件,修改文件中的数据,缘由在于flock只是用于检测文件是否被加锁,针对文件已经被加锁,另外一个进程写入数据的状况,内核不会阻止这个进程的写入操做,也就是建议性锁的内核处理策略。
flock主要三种操做类型:
LOCK_SH,共享锁,多个进程可使用同一把锁,常被用做读共享锁;
LOCK_EX,排他锁,同时只容许一个进程使用,常被用做写锁;
LOCK_UN,释放锁;
进程使用flock尝试锁文件时,若是文件已经被其余进程锁住,进程会被阻塞直到锁被释放掉,或者在调用flock的时候,采用LOCK_NB参数,在尝试锁住该文件的时候,发现已经被其余服务锁住,会返回错误,errno错误码为EWOULDBLOCK。即提供两种工做模式:阻塞与非阻塞类型。
服务会阻塞等待直到锁被释放:
flock(lockfd,LOCK_EX)
服务会返回错误发现文件已经被锁住时:
ret = flock(lockfd,LOCK_EX|LOCK_NB)
同时ret = -1, errno = EWOULDBLOCK
flock锁的释放很是具备特点,便可调用LOCK_UN参数来释放文件锁,也能够经过关闭fd的方式来释放文件锁(flock的第一个参数是fd),意味着flock会随着进程的关闭而被自动释放掉。
flock其中的一个使用场景为:检测进程是否已经存在;git
int checkexit(char* pfile) { if (pfile == NULL) { return -1; } int lockfd = open(pfile,O_RDWR); if (lockfd == -1) { return -2; } int iret = flock(lockfd,LOCK_EX|LOCK_NB); if (iret == -1) { return -3; } return 0; }
1. 场景概述github
在多线程开发中,互斥锁能够用于对临界资源的保护,防止数据的不一致,这是最为广泛的使用方法。那在多进程中如何处理文件之间的同步呢?咱们看看下面的图:多线程
图中所示的是两个进程在无同步的状况下同时更新同一个文件的过程,其主要的操做是:并发
从图中可得知两个进程读取分别增长了所读取到的序号,并写回到了文件中,可是若是有相互互斥的话,最后的值应该是1002,而不是所示的1001。为了防止出现这种状况,Linux提供了flock(对整个文件加锁)、fcntl(对整个文件区域加锁)两个函数来作进程间的文件同步。同时也可使用信号量来完成所需的同步,但一般使用文件锁会更好一些,由于内核可以自动将锁与文件关联起来。函数
2. flock()spa
flock的声明以下线程
1
2
3
4
|
#include <sys/file.h>
// Returns 0 on success, or -1 on error
int flock (intfd, int operation);
|
fcntl()函数提供了比该函数更为强大的功能,而且所拥有的功能也覆盖了flock()所拥有的功能,可是在某些应用中任然使用着flock()函数,而且在继承和锁释放方面的一些语义 中flock()与fcntl()仍是有所不一样的。code
flock()系统调用是在整个文件中加锁,经过对传入的fd所指向的文件进行操做,而后在经过operation参数所设置的值来肯定作什么样的操做。operation能够赋以下值:blog
在默认状况下,若是另外一个进程已经持有了文件上的一个不兼容的锁,那么flock()会阻塞。若是须要防止这种状况的出现,能够在operation参数中对这些值取OR(|)。在这种状况下,若是一个进程已经持有了文件上的一个不兼容锁,那么flock()就会阻塞,相反,它会返回-1,并将errno设置成EWOULDBLOCK。继承
任意数量的进程可同时持有一个文件上的共享锁,但子任意时刻只能有一个进程可以持有一个文件上的互斥锁,(这有点相似读写锁)。下图是进程A先设置了锁,进程B后设置锁的支持状况:
不管程序以什么模式打开了文件(读、写或者读写),该文件上均可以放置一把共享锁或互斥锁。在实际操做过程当中,参数operation能够指定对应的值将共享锁转换成互斥锁(反之亦然)。将一个共享锁转换成互斥锁,若是另外一个进程要获取该文件的共享锁则会阻塞,除非operation参数指定了LOCK_NB标记,即:(LOCK_SH | LOCK_NB)。锁的转换过程不是一个原子操做,在转换的过程当中首先会删除既有的锁,而后建立新锁。
3. 锁继承与释放的语义
flock()根据调用时operation参数传入LOCK_UN的值来释放一个文件锁。此外,锁会在相应的文件描述符被关闭以后自动释放。同时,当一个文件描述符被复制时(dup()、dup2()、或一个fcntl() F_DUPFD操做),新的文件描述符会引用同一个文件锁。
1
2
3
|
flock(fd, LOCK_EX);
new_fd = dup(fd);
flock(new_fd, LOCK_UN);
|
这段代码先在fd上设置一个互斥锁,而后经过fd建立一个指向相同文件的新文件描述符new_fd,最后经过new_fd来解锁。从而咱们能够得知新的文件描述符指向了同一个锁。因此,若是经过一个特定的文件描述符获取了一个锁而且建立了该描述符的一个或多个副本,那么,若是不显示的调用一个解锁操做,只有当文件描述符副本都被关闭了以后锁才会被释放。
由上咱们能够推出,若是使用fork()建立一个子进程,子进程会复制父进程中的全部描述符,从而使得它们也会指向同一个文件锁。例以下面的代码会致使一个子进程删除一个父进程的锁:
1
2
3
4
5
|
flock (fd, LOCK_EX);
if (0 == fork ()) {
flock (fd, LOCK_UN);
}
|
因此,有时候能够利用这些语义来将一个文件锁从父进程传输到子进程:在fork()以后,父进程关闭其文件描述符,而后锁就只在子进程的控制之下了。经过fork()建立的锁在exec()中会得以保留(除非在文件描述符上设置了close-on-exec标记而且该文件描述符是最后一个引用底层的打开文件描述的描述符)。
若是程序中使用open()来获取第二个引用同一个文件的描述符,那么,flock()会将其视为不一样的文件描述符。以下代码会在第二个flock()上阻塞。
1
2
3
4
5
|
fd1 = open ("test.txt", O_RDWD);
fd2 = open ("test.txt", O_RDWD);
flock (fd1, LOCK_EX);
flock (fd2, LOCK_EX);
|
4. flock()的限制
flock()放置的锁有以下限制
注释:在默认状况下,文件锁是劝告式的,这表示一个进程能够简单地忽略另外一个进程在文件上放置的锁。要使得劝告式加锁模型可以正常工做,全部访问文件的进程都必需要配合,即在执行文件IO以前先放置一把锁。