1、锁的应用场景
假设有一个文件a,它有1000个字节,一个进程A打开a文件并使用lseek定位到文件到末尾的位置,准备写50个字节,同时进程B也打开这个a文件进行和进程A同样的操作,那么文件最后的内容并不是1000+50+50个字节,而是1050,两个进程后写入的内容将会覆盖前面写的内容,那么如何解决这种问题呢?
这种情况下,文件锁应运而生。
2、建议性锁
建议性锁flock,不具备强制性。一个进程使用flock将文件锁住,另一个进程可以直接操作正在被锁的文件,修改文件中的数据,原因在于flock只是用于检测文件是否被加锁,针对文件已经被加锁,另一个进程写入数据的情况,内核不会阻止这个进程的写入操作,也就是建议性锁的内核处理策略。
flock主要三种操作类型:
LOCK_SH,共享锁,多个进程可以使用同一把锁,常被用作读共享锁;
LOCK_EX,排他锁,同时只允许一个进程使用,常被用作写锁;
LOCK_UN,释放锁;
进程使用flock尝试锁文件时,如果文件已经被其他进程锁住,进程会被阻塞直到锁被释放掉,或者在调用flock的时候,采用LOCK_NB参数,在尝试锁住该文件的时候,发现已经被其他服务锁住,会返回错误,errno错误码为EWOULDBLOCK。即提供两种工作模式:阻塞与非阻塞类型。
多个锁同时存在的状态如下:
| LOCK_SH | LOCK_EX
-----------------------------------------------------------------------
LOCK_EX | NO | NO
LOCK_SH | YES | NO
3、建议性锁的继承与释放
flock()根据调用时operation参数传入LOCK_UN的值来释放一个文件锁。
此外,锁会在相应的文件描述符被关闭之后自动释放。
如下代码都会释放建议性锁:
flock(fd, LOCK_EX); flock(fd, LOCK_UN);
flock(fd, LOCK_EX);
fclose(fd); //这种情况只试用于当前打开的文件只有一个fd指向它。
当一个文件描述符被复制时(dup()、dup2()、或一个fcntl() F_DUPFD操作),新的文件描述符会引用同一个文件锁。
flock(fd, LOCK_EX); new_fd = dup(fd); flock(new_fd, LOCK_UN);
这段代码先在fd上设置一个互斥锁,然后通过fd创建一个指向相同文件的新文件描述符new_fd,最后通过new_fd来解锁。从而我们可以得知新的文件描述符指向了同一个锁。所以,如果通过一个特定的文件描述符获取了一个锁并且创建了该描述符的一个或多个副本,那么,如果不显示的调用一个解锁操作,只有当文件描述符副本都被关闭了之后锁才会被释放。
同理,当fork()一个子进程后,子进程会复制父进程中的所有描述符,从而使得它们也会指向同一个文件锁。
flock (fd, LOCK_EX); if (0 == fork ()) { flock (fd, LOCK_UN); }
如上这段代码子进程也会释放父进程的文件锁。当然,如果文件描述符设置了close-on-exec标志且子进程执行了exec(),文件描述符被关闭,子进程不再拥有文件描述符情况除外。
4、建议性锁的限制
只能对整个文件进行加锁。这种粗粒度的加锁会限制协作进程间的并发。假如存在多个进程,其中各个进程都想同时访问同一个文件的不同部分。
通过flock()只能放置劝告式锁。
5、强制性锁
是OS内核的文件锁。每个对文件操作时,例如执行open、read、write等操作时,OS内部检测该文件是否被加了强制锁,如果加锁导致这些文件操作失败。也就是内核强制应用程序来遵守游戏规则;
注意:操作系统默认情况下使用的是建议性锁,所以在日常开发中执行文件IO先放一把建议性锁。