2012-07-23 28 views
8

我需要將文件從一個位置複製到另一個位置,並且如果該文件已存在於目標(不覆蓋),我需要引發異常(或至少以某種方式識別)。安全的原子文件複製操作

我可以先用os.path.exists()檢查,但是在檢查和複製之間不能在少量時間內創建文件是非常重要的。

有沒有這樣做的內置方式,或者有沒有辦法將動作定義爲原子?

+0

它只是創建需要是原子的目的地,但也有源內容,如閱讀,只代表一個時間點? – 2012-07-23 14:57:49

+0

只是創造。我正在編寫一個程序,將區域文件複製到/ tmp,進行必要的更改,然後在最後複製它。我只需要確定兩個人是否同時嘗試和編輯,其中一個人不會失去他們的變化。 – Rory 2012-07-23 15:10:55

+2

請注意,如果源和目標位於同一個文件系統上,那麼'rename()'只是原子 - 所以您可能想要在目標目錄中創建臨時文件,而不是在'/ tmp'中。 – 2012-07-23 15:26:52

回答

7

其實辦法做到這一點,原子和安全,提供的所有演員都以相同的方式。這是lock-free whack-a-mole algorithm的適應,而不是完全微不足道的,可以隨意去與「無」爲一般的答案;)

怎麼辦

  1. 檢查文件是否已經存在。如果它確實停止。
  2. Generate a unique ID
  3. 將源文件複製到具有臨時名稱的目標文件夾,如<target>.<UUID>.tmp
  4. 重命名副本<target>-<UUID>.mole.tmp
  5. Look for any other files matching the pattern<target>-*.mole.tmp
    • 如果他們的UUID比你大,attempt to delete it。 (不要擔心,如果它消失了。)
    • 如果他們的UUID比較小於你的,嘗試刪除你自己的。 (再次,不要擔心,如果它消失了。)從現在起,把他們的UUID當作自己的UUID來對待。
  6. 再次檢查目的地文件是否已經存在。如果是這樣,請嘗試刪除臨時文件。 (不要擔心,如果它消失了,請記住您的UUID在步驟5中可能已更改。)
  7. 如果您尚未嘗試在步驟6中刪除它,請嘗試將您的臨時文件重命名爲其最終名稱<target>。 (不要擔心,如果它消失了,只需跳回步驟5.)

你完成了!

它是如何工作

試想每個候選源文件即將出洞的痣。在中途停下來之前,它會暫停並將任何競爭性的痣撥回地面,然後檢查是否沒有其他痣完全出現。如果你在腦中運行這個過程,你應該看到只有一個痣會使它完全消失。爲了防止這個系統從livelocking,我們添加一個總的順序,哪個鼴鼠可以擊中哪個。巴姆! A  博士論文  lock-free algorithm

第4步可能看起來不必要—爲什麼不只是在第一個地方使用該名稱?但是,另一個過程可能會在步驟5中「採用」您的     文件,並使其成爲第7步中的贏家,所以非常重要的是您不會寫出內容!在同一個文件系統上重命名是原子的,所以第4步是安全的。

+1

這是一個漂亮的算法。我不認爲我會這麼做IRL,但這種方法很巧妙。 – Rory 2015-05-12 18:26:57

+0

這是否依賴uuid產生增量值? – coolfeature 2016-10-03 13:33:45

+0

@coolfeature不,訂購僅用於確保最終選擇贏家。 – 2016-10-03 15:46:02

11

有沒有辦法做到這一點;文件複製操作從來不是原子的,也沒有辦法使它們成爲可能。

但是你可以將該文件寫入一個隨機的臨時名稱,然後重命名爲它。重命名操作必須是原子操作。 如果文件已經存在,重命名將會失敗,並且會出現錯誤。

[編輯2]rename()只有在同一文件系統中執行時纔是原子。安全的方法是在與目的地相同的文件夾中創建新文件。

[編輯]有很多討論重新命名是否總是原子性和關於覆蓋行爲。所以我挖掘了一些資源。

在Linux上,如果目標存在,並且源和目標都是文件,則目標將被自動覆蓋(man page)。所以我錯了。

但是rename(2)仍然保證原始文件或新文件在出現問題時保持有效,所以操作是原子的,因爲它不會破壞數據。從某種意義上說,它不是原子的,它可以防止兩個進程同時進行相同的重命名,並且可以預測結果。一個會贏,但你不知道哪個。

在Windows上,如果另一個進程當前正在寫入該文件,如果您嘗試打開該文件以進行寫入,則會出現錯誤,因此Windows的優勢如下。

如果您的計算機在操作寫入磁盤時崩潰,則文件系統的實現將決定有多少數據被損壞。有沒有什麼一個應用程序可以做到這一點。所以停止嗚嗚聲已經:-)

也沒有其他的方法,更好地工作,甚至就像這一個。

您可以使用文件鎖定代替。但是這樣做會使一切變得更加複雜,並且不會產生額外的優勢(除了由於某些原因,一些人認爲這是一個巨大的優勢更復雜)。當你的文件在網絡驅動器上時,你會添加很多不錯的角落案例。

如果文件已經存在,您可以使用open(2)的模式O_CREAT,這將導致函數失敗。但這並不妨礙第二個進程刪除該文件並編寫自己的副本。

或者你可以創建一個鎖目錄,因爲創建目錄也必須是原子的。但那也不會讓你買得太多。你必須自己編寫鎖代碼並絕對保證,100%確定你真的,真的會在發生災難的情況下刪除鎖目錄 - 你不能。

+0

只有在重命名之前刷新和同步。 – dcolish 2012-07-23 14:47:31

+0

出於好奇,這部作品的重命名部分如何? 'os.rename'在Unix上不起作用。 – mgilson 2012-07-23 14:49:13

+0

@dcolish不需要同步 - 沖洗或close()就足夠了。 – 2012-07-23 14:49:23