老谭笔记

解决NSDistributedLock进程互斥锁的死锁问题(二)

上一篇文章中介绍了采用了文件记录锁来实现更加安全的多进程互斥,它的平台兼容性也非常好,并且我们也采用它实现了NSDistributedLock的所有的方法.
其实在OSX还可以采用文件读写锁来实现更加方便的进程互斥,在fcntl.h中我们可以看到这样的宏定义:

1
2
3
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
#define O_SHLOCK 0x0010 /* open with shared file lock */
#define O_EXLOCK 0x0020 /* open with exclusive file lock */

这些宏是同O_RDONLY,O_WRONLY等一样,都是用于打开文件时使用的掩码,也就是说我们可以在open文件的时候加上这些掩码来实现读写互斥,具体的规则就是(适用于多进程或多线程):
只要没有写模式下的加锁,就可以进行读模式下的加锁;
只有读写锁处于不加锁状态时,才能进行写模式下的加锁;

当然我们大多数情况下都只是简单的资源独占的互斥,所以直接采用写模式下的互斥便可,例如当进程A有如下执行了操作:

1
open(filepath, O_CREAT|O_EXLOCK,0666);

其它进程再要做同样操作时就会进入阻塞状态,直到进程A中close了文件描述符或进程退出.

因为同样适用于多线程,所以我们可以实现以下阻塞版本的NSDistributedLock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@interface QMDistributedLock : NSObject<NSLocking>
{
NSString *filePath;
int fileID;
}
- (instancetype)initWithPath:(NSString *)path;
@end
@implementation QMDistributedLock
- (instancetype)initWithPath:(NSString *)path
{
self = [super init];
if (self)
{
filePath = [path copy];
if (!filePath)
return nil;
NSFileManager *fileMgr = [NSFileManager defaultManager];
NSString *parentPath = [filePath stringByDeletingLastPathComponent];
//父目录不存在
if (![fileMgr fileExistsAtPath:parentPath])
return nil;
//父目录不可写
if (![fileMgr isWritableFileAtPath:parentPath])
return nil;
//已经存在该名字的目录
BOOL isDirectory = NO;
if ([fileMgr fileExistsAtPath:filePath isDirectory:&isDirectory] && isDirectory)
{
if (![fileMgr removeItemAtPath:filePath error:NULL])
return nil;
}
}
return self;
}
- (void)lock
{
fileID = open([filePath fileSystemRepresentation], O_CREAT|O_EXLOCK,0666);
}
- (void)unlock
{
close(fileID);
}