这是Ruby代码:
File.open('a.txt', File::CREAT | File::RDWR) do |f|
# Another thread deletes the a.txt file here
f.flock(File::LOCK_EX | File::LOCK_NB)
# How do I check that the file is really locked by my thread?
end
在多线程环境中,当其中许多试图锁定文件然后在一个线程之后将其删除时,可以将其删除在flock()
调用之前。在这种情况下,flock()
仍然认为该文件已就位并返回true
。
我正在尝试找到一种方法来检查文件flock()
之后是否立即被当前线程真正锁定。我该怎么办?
答案 0 :(得分:1)
如果f.flock(File::LOCK_EX | File::LOCK_NB)
返回非false
值,则f
被锁定。它将保持锁定状态,直到您关闭文件或显式调用f.flock(File::LOCK_UN)
。您不必检查它是否再次被锁定。为了解释实际发生的情况,我们首先需要研究文件系统内部和相关的系统调用:
File Descriptor Table Open File Table i-node Table Directory Index
╒════════════════════╕ ╒═════════════╕ ╒════════════╕ ╒═════════════╕
┃3 ..................┣━━━━━━▷┃ open file1 ┣━━┳━━━▷┃ /tmp/file1 ┃◃━━━━┫ file1 ┃
┃4 ..................┣━━━━━━▷┃ open file1 ┣━━┚ ┏━▷┃ /tmp/file2 ┃◃━━━━┫ file2 ┃
┃5 ..................┣━━━┳━━▷┃ open file2 ┣━━━━┚
┃6 ..................┣━━━┚
此图中的关键点是 i节点表中有两个不同且不相关的入口点:打开文件表和目录索引。不同的系统调用与不同的入口点一起工作:
此处的关键点是 unlink 不一定立即删除文件(数据)!它仅取消链接目录索引和索引节点表。这意味着即使在取消链接之后,该文件仍可能会打开并带有活动锁!
请记住,想象一下以下具有2个线程的场景,尝试使用open / flock / close在文件上进行同步,并尝试使用unlink进行清理:
THREAD 1 THREAD 2
==================================================
| |
| |
(1) OPEN (file1, CREATE) |
| (1) OPEN (file1, CREATE)
| |
(2) LOCK-EX (FD1->i-node-1) |
[start work] (2) LOCK-EX (FD2->i-node-1) <---
| . |
| . |
(3) work . |
| (3) waiting loop |
| . |
[end work] . |
(4) UNLINK (file1) . -----------------------
(5) CLOSE (FD1)--------unlocked------> [start work]
| |
| |
(6) OPEN (file1, CREATE) |
| |
| (5) work
(7) LOCK-EX (FD1->i-node-2) |
[start work] !!! does not wait |
| |
(8) work |
| |
在上述情况下,问题在于打开/取消链接对目录索引起作用,而锁定/关闭对文件描述符起作用,而这两个彼此无关。
要解决此问题,我们需要通过某个中央入口点同步这些操作。可以通过引入单例服务来实现,该服务将使用Mutex或{{3}中的基元来提供此同步。 }。
这是一种可能的PoC实现方式:
class FS
include Singleton
def initialize
@mutex = Mutex.new
@files = {}
end
def open(path)
path = File.absolute_path(path)
file = nil
@mutex.synchronize do
file = File.open(path, File::CREAT | File::RDWR)
ref_count = @files[path] || 0
@files[path] = ref_count + 1
end
yield file
ensure
@mutex.synchronize do
file.close
ref_count = @files[path] - 1
if ref_count.zero?
FileUtils.rm(path, force: true)
@files.delete(path)
else
@files[path] = ref_count
end
end
end
end
这是您从问题中重写的示例:
FS.instance.open('a.txt') do |f|
if f.flock(File::LOCK_EX | File::LOCK_NB)
# you can be sure that you have a lock
end
# 'a.txt' will finally be deleted
end