C++ 最优锁定文件方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1599459/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Optimal lock file method
提问by chmike
Windows has an option to open a file with exclusive access rights. Unix doesn't.
Windows 可以选择以独占访问权限打开文件。Unix 没有。
In order to ensure exclusive access to some file or device, it is common practice in Unix to use a lock file usually stored in the /var/lock directory.
为了确保对某些文件或设备的独占访问,Unix 中的常见做法是使用通常存储在 /var/lock 目录中的锁定文件。
The C instruction open( "/var/lock/myLock.lock", O_RDWR | O_CREAT | O_EXCL, 0666 )
returns -1 if the lock file already exist, otherwise it creates it. The function is atomic and ensures there is not race condition.
open( "/var/lock/myLock.lock", O_RDWR | O_CREAT | O_EXCL, 0666 )
如果锁定文件已经存在,C 指令返回 -1,否则创建它。该函数是原子的,并确保没有竞争条件。
When the resource is released, the lock file is deleted by the following instruction
remove( "/var/lock/myLock.lock" )
.
当资源被释放时,通过以下指令删除锁文件
remove( "/var/lock/myLock.lock" )
。
There are two problems with this method.
这种方法有两个问题。
The program may terminate without removing the lock. For instance because it is killed, crashes or whatever. The lock file remains in place, and will prevent any access to the resource even though it is not used anymore.
The lock file is created with group and world write privilege but it is a common practice to configure accounts to use a permission mask that will clear the group and world write permission. Thus, if we had a reliable method to determine that the lock is orphan (not use), a user not owner of the file will not be allowed to remove it.
程序可以在不移除锁的情况下终止。例如,因为它被杀死,崩溃或其他什么。锁定文件保留在原位,即使不再使用该资源,也将阻止对该资源的任何访问。
锁定文件是使用组和全局写入权限创建的,但通常的做法是将帐户配置为使用权限掩码来清除组和全局写入权限。因此,如果我们有一种可靠的方法来确定锁是孤立的(未使用),则不允许不是文件所有者的用户删除它。
For the record, I use the lock file to ensure exclusive access to the device connected to the serial port (/dev/ttyUSBx in fact). Advisory method, requiring cooperation, is OK. But exclusive access should be ensured between different users.
作为记录,我使用锁定文件来确保对连接到串行端口的设备(实际上是 /dev/ttyUSBx)的独占访问。咨询方式,需要配合,可以。但应确保不同用户之间的独占访问。
Is there a better synchronization method than the lock file? How to determine if the process who created the lock file is still running? How to make it possible for another user to remove the lock file if not in use?
有没有比锁文件更好的同步方法?如何确定创建锁定文件的进程是否仍在运行?如果不使用锁定文件,如何让其他用户可以删除锁定文件?
One solution I came up with was to use the file as Unix socket file. If the file exist, try to connect using the file. If it fails, we may assume the owner process of the file is dead. This requires to have a thread looping on socket accept()
in the owner process. Unfortunately, the system wouldn't be atomic anymore.
我想出的一种解决方案是将该文件用作 Unix 套接字文件。如果文件存在,请尝试使用该文件进行连接。如果失败,我们可以假设文件的所有者进程已经死了。这需要accept()
在所有者进程中的套接字上有一个线程循环。不幸的是,该系统将不再是原子的。
回答by Andrey Vlasovskikh
Take a look at the enlightening presentation File Locking Tricks and Traps:
看看有启发性的演示文件锁定技巧和陷阱:
This short talk presents several common pitfalls of file locking and a few useful tricks for using file locking more effectively.
这个简短的演讲介绍了文件锁定的几个常见陷阱和一些更有效地使用文件锁定的有用技巧。
Edit:To address your questions more precisely:
编辑:更准确地解决您的问题:
Is there a better synchronization method than the lock file?
有没有比锁文件更好的同步方法?
As @Hasturkun already mentioned and as the presentation above told, the system call you need to use is flock(2)
. If the resource you'd like to share across many users is already file-based (in your case it is /dev/ttyUSBx
), then you can flock
the device file itself.
正如@Hasturkun 已经提到的,正如上面的演示所说,您需要使用的系统调用是flock(2)
. 如果您想在许多用户之间共享的资源已经是基于文件的(在您的情况下是/dev/ttyUSBx
),那么您可以flock
使用设备文件本身。
How to determine if the process who created the lock file is still running?
如何确定创建锁定文件的进程是否仍在运行?
You don't have to determine this, as the flock
-ed lock will be automatically released upon closing the file descriptor associated with your file, even if the process was terminated.
您不必确定这一点,因为flock
-ed 锁将在关闭与您的文件关联的文件描述符时自动释放,即使进程已终止。
How making it possible for another user to remove the lock file if not in use?
如果不使用锁定文件,如何让其他用户可以删除锁定文件?
If you would lock the device file itself, then there will be no need to remove the file. Even if you would decide to lock an ordinary file in /var/lock
, with flock
you will not need to remove the file in order to release the lock.
如果您要锁定设备文件本身,则无需删除该文件。即使您决定在 中锁定一个普通文件/var/lock
,flock
您也不需要删除该文件来释放锁定。
回答by Hasturkun
回答by chmike
The answer of Hasturkun is the one that has put me on track.
Hasturkun 的答案是让我走上正轨的答案。
Here is the code I use
这是我使用的代码
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <fcntl.h>
/*! Try to get lock. Return its file descriptor or -1 if failed.
*
* @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
* @return File descriptor of lock file, or -1 if failed.
*/
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;
}
/*! Release the lock obtained with tryGetLock( lockName ).
*
* @param fd File descriptor of lock returned by tryGetLock( lockName ).
* @param lockName Name of file used as lock (i.e. '/var/lock/myLock').
*/
void releaseLock( int fd, char const *lockName )
{
if( fd < 0 )
return;
remove( lockName );
close( fd );
}
回答by Miika Karanki
Be careful with lock and release lock functions implemented like mentioned in one of the answers, i.e. like this:
小心使用其中一个答案中提到的锁定和释放锁定功能,即像这样:
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;
}
and:
和:
void releaseLock( int fd, char const *lockName )
{
if( fd < 0 )
return;
remove( lockName );
close( fd );
}
The problem is that releaseLock's remove call introduces a race situation bug. Let's consider there are three processes all trying to acquire the exclusive flock with nasty timing:
问题是 releaseLock 的 remove 调用引入了竞争情况错误。让我们考虑三个进程都试图以令人讨厌的时间获取独占羊群:
- Process #1 has opened the lock file, acquired the flock, and is about to call the unlock function but not yet done that.
- Process #2 has called open to open the file pointed be lockName, and has got a file descriptor of it, but not yet called flock. That is, the file pointed by lockName is opened two times now.
- Process #3 is not yet started.
- 进程#1 已经打开了锁定文件,获取了羊群,并且即将调用解锁函数但还没有完成。
- 进程#2 调用open 打开指向lockName 的文件,并获得了它的文件描述符,但还没有调用flock。也就是说,lockName 指向的文件现在被打开了两次。
- 过程#3 尚未启动。
With nasty timing, it is possible that process #1 calls first remove() and close() (the order doesn't matter), and then process #2 calls the flock using the file descriptor already opened, but which is not anymore the file named lockName but a file descriptor that is not linked to any directory entry.
由于时间不好,进程#1 可能首先调用remove() 和close()(顺序无关紧要),然后进程#2 使用已经打开的文件描述符调用flock,但这不再是名为 lockName 的文件,但没有链接到任何目录条目的文件描述符。
Now, if the process #3 is started, the open() call of it creates the lockName file, and acquires the lock on it as that file is not locked. As an outcome, processes #2 and #3 both think they both own the lock on fileName -> a bug.
现在,如果进程 #3 启动,它的 open() 调用会创建 lockName 文件,并在该文件未锁定时获取锁定。结果,进程#2 和#3 都认为他们都拥有对文件名的锁 -> 一个错误。
The issue in the implementation is that remove() (or more unlink()) only unlinks the name from the directory entry - the file descriptor referring to that file is still usable. One can create then another file having the same name, but still the already opened fd is referring to a different place.
实现中的问题是 remove()(或更多 unlink())仅从目录条目中取消链接名称 - 引用该文件的文件描述符仍然可用。可以创建另一个具有相同名称的文件,但是已经打开的 fd 仍然指的是不同的地方。
This can be demonstrated adding delay to the lock function:
这可以证明为锁定功能添加延迟:
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;
}
}
- Try starting process #1, and hit enter once to acquire the lock.
- Then start process #2 on another terminal,
- Press another enter on the terminal where process #1 is running to release the lock. 4. Continue with process #2 by pressing enter once so that it acquires the lock.
- Then open another terminal where to run process #3. In there, press enter once to acquire the lock.
- 尝试启动进程 #1,然后按 Enter 一次以获取锁。
- 然后在另一个终端上启动进程#2,
- 在进程 #1 正在运行的终端上按另一个输入以释放锁定。4. 继续进程#2,按一次回车键以获取锁。
- 然后打开另一个终端来运行进程#3。在那里,按 Enter 一次以获取锁。
The "impossible" happens: processes #2 and #3 think they both have the exclusive lock.
“不可能”发生了:进程#2 和#3 认为它们都有排他锁。
This might be rare to happen in practice at least with usual applications, but nevertheless the implementation is not correct.
至少在通常的应用程序中,这在实践中可能很少发生,但是实现是不正确的。
Also, creating a file with mode 0666 might be a security risk.
此外,创建模式为 0666 的文件可能存在安全风险。
I don't have "reputation to comment", and this is also quite old question, but people are still referring to this and doing something similar, so that's why to add this note as an answer.
我没有“评论的声誉”,这也是一个很老的问题,但人们仍然在参考这个并做类似的事情,所以这就是为什么添加这个注释作为答案的原因。
回答by Hymanson
To expand on Hasturhun's answer. Instead of using the presence or absence of the lock file as an indicator, you need to both create the lock file if it dosen't exists and then get an exclusive lock on the file.
扩展 Hasturhun 的回答。不是使用锁定文件的存在与否作为指示符,您需要在它不存在时创建锁定文件,然后在文件上获得排它锁。
The advantages of this approach is that unlike many other methods of syncing programs, the OS should tidy up for you if your program exits without unlocking.
这种方法的优点是,与许多其他同步程序的方法不同,如果您的程序没有解锁就退出,操作系统应该为您整理。
So the program structure would be something like:
所以程序结构将是这样的:
1: open the lock file creating it if it doesn't exist
2: ask for an exclusive lock an agreed byte range in the lock file
3: when the lock is granted then
4: <do my processing here>
5: release my lock
6: close the lock file
end
At step: you can either block waiting for the lock to be granted or return immediately. The bytes you lock don't actually have to exist in the file. If you can get hold of a copy of Advanced Unix Programmingby Marc J. Rochkind, he develops a complete C library that uses this method to provide a way of syncing programs that gets tidied up by the OS, however the programs exit.
在步骤:您可以阻塞等待锁定被授予或立即返回。您锁定的字节实际上不必存在于文件中。如果您可以获得Marc J. Rochkind的Advanced Unix Programming的副本,他会开发一个完整的 C 库,该库使用此方法提供一种同步程序的方法,该程序由操作系统整理,但程序会退出。
回答by a.grochmal
I was using the code posted by chmike, and noticed one small imperfection. I had a problem with the race during the opening lock file. Sometimes, several threads open the lock file simultaneously.
我正在使用 chmike 发布的代码,并注意到一个小缺陷。我在打开锁定文件期间遇到了比赛问题。有时,多个线程同时打开锁定文件。
Therefore, I removed the "remove( lockName );" line from "releaseLock()" function. I do not understand why, but somehow this action helped the situation.
因此,我删除了“remove(lockName);” 来自“releaseLock()”函数的行。我不明白为什么,但不知何故这个行动帮助了这种情况。
I have been using the following code to test the lock files. By its output one can see when several threads start using one lock simultaneously.
我一直在使用以下代码来测试锁定文件。通过它的输出可以看到几个线程同时开始使用一个锁。
void testlock(void) {
# pragma omp parallel num_threads(160)
{
int fd = -1; char ln[] = "testlock.lock";
while (fd == -1) fd = tryGetLock(ln);
cout << omp_get_thread_num() << ": got the lock!";
cout << omp_get_thread_num() << ": removing the lock";
releaseLock(fd,ln);
}
}