簡體   English   中英

如何檢查文件是否仍被當前線程鎖定?

[英]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 表中有兩個不同且不相關的入口點:打開文件表和目錄索引。 不同的系統調用使用不同的入口點:

  • open(file_path) => 從 Directory Index 中找到 i-node 編號,並在 File Descriptor Table 引用的 Open File Table 中創建一個條目(每個進程一個表),然后在相關的 i-node Table 條目中增加 ref_counter。
  • close(file_descriptor) => 從 Open File Table 關閉(釋放)相關的 File Descriptor Table 條目和相關條目(除非有其他引用文件描述符),然后減少相關 i-node Table 條目中的 ref_counter(除非 Open File 條目保持打開狀態)
  • unlink(file_path) => 沒有刪除系統調用! 通過從目錄索引中刪除條目,從目錄索引中取消索引節點表的鏈接。 減少相關 i-node 表條目中的計數器(不知道打開文件表!)
  • flock(file_desriptor) => 對打開文件表中的條目應用/移除鎖定(不知道目錄索引!)
  • i-node 表條目被刪除(實際上是刪除一個文件) IFF ref_counter 變為零。 它可能發生在 close() 或 unlink() 之后

這里的關鍵是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                                   |
       |                                    |
  • (1) 兩個線程都打開(可能創建)同一個文件。 因此,存在從目錄索引到索引節點表的鏈接。 每個線程都有自己的文件描述符。
  • (2) 兩個線程都嘗試使用從 open 調用中獲得的文件描述符來獲得排他鎖
  • (3) 第一個線程獲得鎖而第二個線程被阻塞(或試圖在循環中獲得鎖)
  • (4) 第一個線程完成一個任務並刪除(取消鏈接)一個文件。 此時從目錄索引到 i-node 的鏈接被刪除,我們不會在目錄列表中看到它。 但是,該文件仍然存在,並且在兩個線程中打開並帶有活動鎖! 它只是失去了它的名字。
  • (5) 第一個線程關閉文件描述符並因此釋放鎖。 因此第二個線程獲得鎖並開始處理任務
  • (6) 第一個線程重復並嘗試打開同名文件。 但它和以前的文件一樣嗎? 否。因為此時目錄索引中沒有給定名稱的文件。 所以它會創建一個新文件! 新的索引節點表條目。
  • (7) 第一個線程獲得一個新文件的鎖!
  • (8) 我們得到了兩個線程,它們鎖定了兩個不同的文件並且未同步

上述場景中的問題是打開/取消鏈接對目錄索引起作用,而鎖定/關閉對文件描述符起作用,它們彼此不相關。

為了解決這個問題,我們需要通過一些中央入口點來同步這些操作。 它可以通過引入一個單例服務來實現,該服務將使用來自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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM