小心使用像某个答案中所提到的锁定和释放锁定函数的实现方式,即像这样:
int tryGetLock( char const *lockName )
{
mode_t m = umask( 0 );
int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
umask( m );
if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
{
close( fd );
fd = -1;
}
return fd;
}
并且:
void releaseLock( int fd, char const *lockName )
{
if( fd < 0 )
return;
remove( lockName );
close( fd );
}
问题在于releaseLock的remove调用引入了一种竞争情况的bug。假设有三个进程都在尝试以不同的时间获得独占锁:
- 进程#1打开了锁文件,获取了flock并即将调用unlock函数,但还没有完成。
- 进程#2调用open打开了指向lockName的文件,并获得了它的文件描述符,但还没有调用flock。也就是说,指向lockName的文件现在已经被打开两次了。
- 进程#3尚未启动。
可能发生的情况是,进程#1在第一次调用remove()和close()(顺序无关紧要)之后,进程#2使用已经打开但不再与任何目录条目相关联的文件描述符调用flock。现在,如果启动进程#3,则其open()调用将创建lockName文件,并针对该文件获取锁定状态,因为该文件尚未被锁定。结果,进程#2和#3都认为他们拥有fileName上的锁 - & gt; 这会导致一个bug。
在实现中的问题在于remove()(或更多的unlink())仅从目录条目中取消链接名称 - 但是引用该文件的文件描述符仍然可以使用。可以创建另一个具有相同名称的文件,但是已经打开的文件描述符仍然指向不同的位置。
可以通过向锁定函数添加延迟来演示这一点:
int tryGetLock( char const *lockName)
{
mode_t m = umask( 0 );
int fd = open( lockName, O_RDWR|O_CREAT, 0666 );
umask( m );
printf("Opened the file. Press enter to continue...");
fgetc(stdin);
printf("Continuing by acquiring the lock.\n");
if( fd >= 0 && flock( fd, LOCK_EX | LOCK_NB ) < 0 )
{
close( fd );
fd = -1;
}
return fd;
}
static const char *lockfile = "/tmp/mylock.lock";
int main(int argc, char *argv[0])
{
int lock = tryGetLock(lockfile);
if (lock == -1) {
printf("Getting lock failed\n");
return 1;
}
printf("Acquired the lock. Press enter to release the lock...");
fgetc(stdin);
printf("Releasing...");
releaseLock(lock, lockfile);
printf("Done!\n");
return 0;
- 尝试启动进程 #1,并按一次回车键来获取锁。
- 然后在另一个终端上启动进程 #2,
- 在运行进程 #1 的终端上按下回车键以释放锁定。4. 通过按一次回车键使进程 #2 获取锁以继续。
- 接下来打开另一个终端来运行进程 #3。在那里,按一次回车键以获取锁。
"不可能" 的事情发生了:进程 #2 和 #3 都认为它们拥有独占锁。
这在实践中可能很少见,至少对于通常的应用程序而言,但是实现并不正确。
此外,使用模式 0666 创建文件可能存在安全风险。
我没有 "评论的声望",而且这也是一个相当旧的问题,但人们仍然参考这个并做类似的事情,所以我作为答案添加这个注释。
fprintf
中完成所有想要做的事情,那么您就完成了。 - EML