在MAC下的多进程开发中,NSDistributedLock是一个非常方便的互斥锁解决方案,一般的使用方法:
12345678 |
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结构的指针。
1234567 |
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:
1234567891011121314151617181920212223 |
- (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:
1234567891011 |
- (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