[英]How to check if the file is still locked by current thread?
這是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()
完成后被當前線程鎖定。 我怎樣才能做到這一點?
如果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-node 表中有兩個不同且不相關的入口點:打開文件表和目錄索引。 不同的系統調用使用不同的入口點:
這里的關鍵是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 |
| |
上述場景中的問題是打開/取消鏈接對目錄索引起作用,而鎖定/關閉對文件描述符起作用,它們彼此不相關。
為了解決這個問題,我們需要通過一些中央入口點來同步這些操作。 它可以通過引入一個單例服務來實現,該服務將使用來自Concurrent Ruby的 Mutex 或原語提供這種同步。
這是一種可能的 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
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.