安全且平台兼容的进程互斥锁

来源:互联网 发布:简单的数据库软件 编辑:程序博客网 时间:2024/04/29 23:46

一种安全且平台兼容的进程互斥锁,它是基于文件记录锁实现的。

1、文件记录锁

UNIX编程的“圣经”《Unix环境高级编程》中有对文件记录锁的详细描述。

下载链接:http://dl02.topsage.com/club/computer/Unix环境高级编程.rar

记录锁(record locking)的功能是:一个进程正在读或修改文件的某个部分时,可以阻止其他进程修改同一文件区。对于UNIX,“记录”这个定语也是误用,因为UNIX内核根本没有使用文件记录这种概念。一个更适合的术语可能是“区域锁”,因为它锁定的只是文件的一个区域(也可能是整个文件)。

2、平台兼容性

各种UNIX系统支持的记录锁形式: 

系统建议性强制性fcntllockfflockPOSIX.1* *  XPG3* *  SVR2* ** SVR3 SVR4**** 4.3BSD* * *4.3BSDReno* * *

可以看成,记录锁在各个平台得到广泛支持。特别的,在接口上,可以统一于fcntl。

建议性锁和强制性锁之间的区别,是指其他文件操作函数(如open,read、write)是否受记录锁影响,如果是,那就是强制性的记录锁,大部分平台只是建议性的。不过,对实现进程互斥锁而言,这个影响不大。

3、接口描述

[cpp] view plaincopy
  1. #include <sys/types.h>  
  2. #include <unistd.h>  
  3. #include <fcnt1.h>  
  4. int fcnt1(int filedes, int cmd, .../* struct flock *flockptr */);  

对于记录锁,cmd是F_GETLK、F_SETLK或F_SETLKW。第三个参数(称其为flockptr)是一个指向flock结构的指针。

[cpp] view plaincopy
  1. struct flock {  
  2.     short l_type;    /* Type of lock: F_RDLCK, F_WRLCK, F_UNLCK */  
  3.     short l_whence;  /* How to interpret l_start: SEEK_SET, SEEK_CUR, SEEK_END */  
  4.     off_t l_start;   /* Starting offset for lock */  
  5.     off_t l_len;     /* Number of bytes to lock */  
  6.     pid_t l_pid;     /* PID of process blocking our lock  
  7. };  

以下说明fcntl函数的三种命令:

  • F_GETLK决定由flockptr所描述的锁是否被另外一把锁所排斥(阻塞)。如果存在一把锁,它阻止创建由flockptr所描述的锁,则这把现存的锁的信息写到flockptr指向的结构中。如果不存在这种情况,则除了将ltype设置为F_UNLCK之外,flockptr所指向结构中的其他信息保持不变。
  • F_SETLK设置由flockptr所描述的锁。如果试图建立一把按上述兼容性规则并不允许的锁,则fcntl立即出错返回,此时errno设置为EACCES或EAGAIN。
  • F_SETLKW这是F_SETLK的阻塞版本(命令名中的W表示等待(wait))。如果由于存在其他锁,那么按兼容性规则由flockptr所要求的锁不能被创建,则调用进程睡眠。如果捕捉到信号则睡眠中断。

4、实现方案

如何 基于记录锁实现进程互斥锁?

1、需要一个定义全局文件名,这个文件名只能有相关进程使用。这需要在整个系统做一个规划。

2、规定同一个进程互斥锁对应着该文件的一个字节,字节位置称为锁的编号,这样可以用一个文件实现很多互斥锁。

3、编号要有分配逻辑,文件中要记录已经分配的编号,这个逻辑也要保护,所以分配0号锁为系统锁。

4、为了实现命名锁,文件中要记录名称与编号对应关系,这个对应关系的维护也需要系统锁保护。

这些逻辑都实现在一个FileLocks类中:

[cpp] view plaincopy
  1. class FileLocks  
  2. {  
  3. public:  
  4.     FileLocks();  
  5.     ~FileLocks();  
  6.     size_t alloc_lock();  
  7.     size_t alloc_lock(std::string const & keyname);  
  8.     void lock(size_t pos);  
  9.     bool try_lock(size_t pos);  
  10.     void unlock(size_t pos);  
  11.     void free_lock(size_t pos);  
  12.     void free_lock(std::string const & keyname);  
  13. private:  
  14.     int             m_fd_;  
  15. };  
  16.   
  17. inline FileLocks & global_file_lock()  
  18. {  
  19.     static FileLocks g_fileblocks( "process.filelock" );  
  20.     return g_fileblocks;  
  21. }  

这里用了一个FileLocks全局单例对象,它对应的文件名是“/tmp/filelock”,在FileLocks中,分别用alloc()和alloc(keyname)分配匿名锁和命名锁,用free_lock删除锁。free_lock(pos)删除匿名锁,free_lock(keyname)删除命名锁。

对锁的使用通过lock、try_lock、unlock实现,他们都带有一个pos参数,代表锁的编号。

有了FileLocks类作为基础,要实现匿名锁和命名锁就很简单了。

4.1、匿名锁

[cpp] view plaincopy
  1. class FileMutex  
  2. {  
  3. public:  
  4.     FileMutex()  
  5.         : m_lockbyte_(global_file_lock().alloc_lock())  
  6.     {  
  7.     }  
  8.     ~FileMutex()  
  9.     {  
  10.         global_file_lock().free_lock(m_lockbyte_);  
  11.     }  
  12.     void lock()  
  13.     {  
  14.         global_file_lock().lock(m_lockbyte_);  
  15.     }  
  16.     bool try_lock()  
  17.     {  
  18.         return global_file_lock().try_lock(m_lockbyte_);  
  19.     }  
  20.     void unlock()  
  21.     {  
  22.         global_file_lock().unlock(m_lockbyte_);  
  23.     }  
  24. protected:  
  25.     size_t m_lockbyte_;  
  26. };  

需要注意的是,进程匿名互斥锁需要创建在共享内存上。只需要也只能某一个进程(比如创建共享内存的进程)调用构造函数,其他进程直接使用,同样析构函数也只能调用一次。

4.2、命名锁

 命名锁只需要构造函数不同,可以直接继承匿名锁实现

[cpp] view plaincopy
  1. class NamedFileMutex   
  2.     : public FileMutex  
  3. {  
  4. public:  
  5.     NamedFileMutex(std::string const & key)  
  6.         : m_lockbyte_(global_file_lock().alloc_lock(key))  
  7.     {  
  8.     }  
  9.     ~NamedFileMutex()  
  10.     {  
  11.         m_lockbyte_ = 0;  
  12.     }  
  13. };  

需要注意,命名锁不住析构时删除,因为可能多个对象共享该锁。

5、线程安全性