在《进程互斥锁》中,我们看到了实现进程互斥锁的几个常见方案:Posix信号量、System V信号量以及线程锁共享,并且分析了他们的平台兼容性以及严重缺陷。这里要介绍
一种安全且平台兼容的进程互斥锁,它是基于文件记录锁实现的。
UNIX编程的“圣经”《Unix环境高级编程》中有对文件记录锁的详细描述。
下载链接:http://dl02.topsage.com/club/computer/Unix环境高级编程.rar
记录锁(record locking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。对于UNIX,“记录”这个定语也是误用,因为UNIX内核根本没有使用文件记录这种概念。一个更适合的术语可能是“区域锁”,因为它锁定的只是文件的一个区域(也可能是整个文件)。
各种UNIX系统支持的记录锁形式:
系统 | 建议性 | 强制性 | fcntl | lockf | flock |
POSIX.1 | * | * | |||
XPG3 | * | * | |||
SVR2 | * | * | * | ||
SVR3 SVR4 | * | * | * | * | |
4.3BSD | * | * | * | ||
4.3BSDReno | * | * | * |
可以看成,记录锁在各个平台得到广泛支持。特别的,在接口上,可以统一于fcntl。
建议性锁和强制性锁之间的区别,是指其他文件操作函数(如open,read、write)是否受记录锁影响,如果是,那就是强制性的记录锁,大部分平台只是建议性的。不过,对实现进程互斥锁而言,这个影响不大。
#include <sys/types.h> #include <unistd.h> #include <fcnt1.h> int fcnt1(int filedes, int cmd, .../* struct flock *flockptr */);
对于记录锁,cmd是F_GETLK、F_SETLK或F_SETLKW。第三个参数(称其为flockptr)是一个指向flock结构的指针。
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 };
以下说明fcntl函数的三种命令:
如何 基于记录锁实现进程互斥锁?
1、需要一个定义全局文件名,这个文件名只能有相关进程使用。这需要在整个系统做一个规划。
2、规定同一个进程互斥锁对应着该文件的一个字节,字节位置称为锁的编号,这样可以用一个文件实现很多互斥锁。
3、编号要有分配逻辑,文件中要记录已经分配的编号,这个逻辑也要保护,所以分配0号锁为系统锁。
4、为了实现命名锁,文件中要记录名称与编号对应关系,这个对应关系的维护也需要系统锁保护。
这些逻辑都实现在一个FileLocks类中:
class FileLocks { public: FileLocks(); ~FileLocks(); size_t alloc_lock(); size_t alloc_lock(std::string const & keyname); void lock(size_t pos); bool try_lock(size_t pos); void unlock(size_t pos); void free_lock(size_t pos); void free_lock(std::string const & keyname); private: int m_fd_; }; inline FileLocks & global_file_lock() { static FileLocks g_fileblocks( "process.filelock" ); return g_fileblocks; }
这里用了一个FileLocks全局单例对象,它对应的文件名是“/tmp/filelock”,在FileLocks中,分别用alloc()和alloc(keyname)分配匿名锁和命名锁,用free_lock删除锁。free_lock(pos)删除匿名锁,free_lock(keyname)删除命名锁。
对锁的使用通过lock、try_lock、unlock实现,他们都带有一个pos参数,代表锁的编号。
有了FileLocks类作为基础,要实现匿名锁和命名锁就很简单了。
class FileMutex { public: FileMutex() : m_lockbyte_(global_file_lock().alloc_lock()) { } ~FileMutex() { global_file_lock().free_lock(m_lockbyte_); } void lock() { global_file_lock().lock(m_lockbyte_); } bool try_lock() { return global_file_lock().try_lock(m_lockbyte_); } void unlock() { global_file_lock().unlock(m_lockbyte_); } protected: size_t m_lockbyte_; };
需要注意的是,进程匿名互斥锁需要创建在共享内存上。只需要也只能某一个进程(比如创建共享内存的进程)调用构造函数,其他进程直接使用,同样析构函数也只能调用一次。
命名锁只需要构造函数不同,可以直接继承匿名锁实现
class NamedFileMutex : public FileMutex { public: NamedFileMutex(std::string const & key) : m_lockbyte_(global_file_lock().alloc_lock(key)) { } ~NamedFileMutex() { m_lockbyte_ = 0; } };
需要注意,命名锁不住析构时删除,因为可能多个对象共享该锁。