老谭笔记

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

在MAC下的多进程开发中,NSDistributedLock是一个非常方便的互斥锁解决方案,一般的使用方法:

1
2
3
4
5
6
7
8
NSDistributedLock *lock = [[NSDistributedLock alloc] initWithPath:@"/Users/mac/Desktop/lock.lock"];
while (![lock tryLock])
{
sleep(1);
}
//do something
[lock unlock];

但在实际使用过程中,当执行到do something时程序退出,程序再次启动之后tryLock就再也不能成功了,陷入死锁状态.这是使用NSDistributedLock时非常隐蔽的风险.其实要解决的问题就是如何在进程退出时会自动释放锁.
在《Unix环境高级编程》中有对record locking(记录锁)的介绍,而这种锁的机制也是基于文件,但它的解锁条件刚好可以解决我们的问题,因为当进程退出时记录锁会自动释放所持有的锁.

fcntl是Unix兼容最好的一种记录锁实现方案,关于它的接口描述在sys/fcntl.h内,函数原型是

1
int fcnt1(int filedes, int cmd, .../* struct flock *flockptr */);

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

1
2
3
4
5
6
7
struct flock {
off_t l_start; /* starting offset */
off_t l_len; /* len = 0 means until end of file */
pid_t l_pid; /* lock owner */
short l_type; /* lock type: read/write, etc. */
short l_whence; /* type of l_start */
};

关于fcntl函数的三种命令描述:
F_GETLK 取得文件锁定的状态。
F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。

用flock实现NSDistributedLock中tryLock:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (BOOL)tryLock
{
int fd = open(path, O_CREAT|O_RDWR,0666);
if (fd < 0)
{
NSLog(@"[err] open fail: %s",strerror(errno));
return NO;
}
struct flock lockinfo;
lockinfo.l_type = F_WRLCK;
lockinfo.l_whence = SEEK_SET;
lockinfo.l_start = 0;
lockinfo.l_len = 0;
int re = fcntl(fd, F_SETLK, &lockinfo);
if (re != 0)
{
close(fd);
return NO;
}
return YES;
}

用flock实现NSDistributedLock中unlock:

1
2
3
4
5
6
7
8
9
10
11
- (void)_unlock
{
struct flock lockinfo;
lockinfo.l_type = F_UNLCK;
lockinfo.l_whence = SEEK_SET;
lockinfo.l_start = 0;
lockinfo.l_len = 0;
fcntl(fd, F_SETLK, &lockinfo);
close(fd);
}

但需要注意的是fcntl实现的记录锁是不支持进程内的多线程间的互斥,为了解决这些问题并完整实现NSDistributedLock中的所有方法和实际表现,下面是我封装的QMDistributedLock:
站内下载:QMDistributedLock.zip