多进程读写文件
如果你想进行资源保护的话,完美的资源保护应该满足如下这样的。
1)写与写之间互斥
2)读与写之间互斥
3)读与读之间共享
使用信号量来实现保护的话,只能是一律互斥,包括读与读都是互斥的,使用文件锁可以做到读读共享,读写互斥,写写互斥。
文件锁的读锁与写锁
①读锁和读锁共享:可以重复加读锁,别人加了读锁在没有解锁之前,我依然可以加读锁,这就是共享。
②读锁与写锁互斥:别人加了读锁没有解锁前,加写锁会失败,反过来也是如此。
③写锁与写锁互斥:别人加了写锁在没有解锁前,不能加写锁,加写锁会失败(阻塞或者返回错误)。
文件锁的加锁方式
(1)对整个文件内容加锁
对整个文件加锁是最常用的文件锁的加锁方式。当你对整个文件加锁时,如果文件的长度因为写入新数据或者截短而发生了变化,加锁内容的长度会自动变化,保证对内容变化着的整个文件加锁。
(2)对文件某部分内容加锁
不过一般来说是,对多少内容加锁,就对多少内容解锁,如果你是对整个文件加锁,就将整个文件解锁。但是实际上加锁和实际解锁的长度可以不相同,比如我对1000个字节的内容加了锁,但是可以只对其中的100字节解锁,不过这种情况用的少,知道有这么回事即可。
文件锁的实现
- fcntl()
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, .../*struct flock *flockptr */ );
fcntl函数有多种功能,我们这里主要介绍实现文件锁的功能,当cmd被设置的是与文件锁相关的宏时,fcntl就是用于实现文件锁。
fd:文件描述符,指向需要被加锁的文件。
cmd:实现文件锁时,cmd有三种设置,F_GETLK、F_SETLK和F_SETLKW含义如下:
F_GETLK:从内核获取文件锁的信息,将其保存到第三个参数,此时第三个参数为struct flock *flockptr。
F_SETLK:设置第三个参数所代表的文件锁,而且设置的是非阻塞文件锁,也就是如果加锁失败不会阻塞。也就是说加锁失败后如果不想阻塞的话,就是由F_SETLK宏来决定的。
F_SETLKW:与F_SETLK一样,只不过设置的是阻塞文件锁,也就说加锁不成功的话就阻塞,是由F_SETLKW宏来决定的。
struct flock { short l_type; // Type of lock: F_RDLCK,F_WRLCK, F_UNLCK short l_whence; //How to interpret l_start:SEEK_SET, SEEK_CUR, SEEK_END off_t l_start; // Starting offset for lock off_t l_len; //Number of bytes to lock pid_t l_pid; //PID of process blocking our lock(F_GETLK only) 当前正加着锁的那个进程的PID,只有当我们获取一个已存在锁的信息时,才会使用这个成员,这个成员的值不是我们设置的,是由文件锁自己设置的,我们只是获取以查看当前那个进程正加着锁。 }
将l_whence和l_start设置为SEEK_SET和0,然后再将l_len设置为0,就表示从文件头加锁到文件末尾,其实就是对整个文件加锁。
flockptr.l_whence=SEEK_SET; flockptr.l_start=0; flockptr.l_len=0; int fd = open(filename, O_RDWR | O_CREAT, LOCKMODE); int lockfile(int fd, int set) { struct flock fl; fl.l_type = F_WRLCK; fl.l_start = set; fl.l_whence = SEEK_SET; fl.l_len = 1; return(fcntl(fd, F_SETLK, &fl)); } int unlockfile(int fd, int set) { struct flock fl; fl.l_type = F_UNLCK; fl.l_start = set; fl.l_whence = SEEK_SET; fl.l_len = 1; return(fcntl(fd, F_SETLK, &fl)); } pid_t checklock(int fd, int set) { struct flock fl; fl.l_type = F_WRLCK; fl.l_start = set; fl.l_whence = SEEK_SET; fl.l_len = 1; if (fcntl(fd, F_GETLK, &fl) == -1) { return -1; } if (fl.l_type == F_UNLCK) { return 0; } return fl.l_pid; }
- flock
#include<sys/file.h> int flock(int fd, int operation); //成功返回0,失败返回-1
fd:指向需要被加锁的文件;
operation:LOCK_SH:加共享锁; LOCK_EX:加互斥锁; LOCK_UN:解锁 。
fd = open("./file", O_RDWR|O_CREAT|O_TRUNC|O_APPEND, 0664);
flock(fd, LOCK_SH);
write(fd, "hello ", 6);
write(fd, "world\n", 6);
flock(fd, LOCK_UN);需要注意的是亲缘进程(父子进程),子进程不能使用从父进程继承而来的文件描述符,父子进程flock时必须使用独自open所返回的文件描述符。这一点与fcntl实现的文件锁不一样,父子进程可以使用各自open返回的文件描述符加锁,但是同时子进程也可以使用从父进程继承而来的文件描述符加锁。
用于多线程时与用于多进程一样,各线程必须使用各自open所返回的文件描述符才能加锁。