Mengunci file dengan Python

152

Saya perlu mengunci file untuk menulis dengan Python. Ini akan diakses dari beberapa proses Python sekaligus. Saya telah menemukan beberapa solusi online, tetapi sebagian besar gagal untuk tujuan saya karena seringkali hanya berbasis Unix atau berbasis Windows.

Evan Fosmark
sumber

Jawaban:

115

Baiklah, akhirnya saya menemukan kode yang saya tulis di sini, di tautan situs web saya sudah mati, lihat di archive.org ( juga tersedia di GitHub ). Saya bisa menggunakannya dengan cara berikut:

from filelock import FileLock

with FileLock("myfile.txt.lock"):
    print("Lock acquired.")
    with open("myfile.txt"):
        # work with the file as it is now locked
Evan Fosmark
sumber
10
Seperti dicatat oleh komentar di posting blog, solusi ini tidak "sempurna", karena program mungkin untuk mengakhiri sedemikian rupa sehingga kunci dibiarkan di tempat dan Anda harus menghapus kunci secara manual sebelum file menjadi dapat diakses kembali. Namun, di samping itu, ini masih merupakan solusi yang baik.
leetNightshade
3
Namun versi lain dari FileLock Evan dapat ditemukan di sini: github.com/ilastik/lazyflow/blob/master/lazyflow/utility/…
Stuart Berg
3
OpenStack memang mempublikasikan implementasi mereka sendiri (well, Skip Montanaro) - pylockfile - Sangat mirip dengan yang disebutkan dalam komentar sebelumnya, tetapi masih layak untuk dilihat.
jweyrich
7
@jweyrich Openstacks pylockfile sekarang tidak digunakan lagi. Disarankan untuk menggunakan pengencang atau oslo.concurrency sebagai gantinya.
harbun
2
Implementasi serupa lainnya yang saya kira: github.com/benediktschmitt/py-filelock
herry
39

Ada modul penguncian file lintas-platform di sini: Portalocker

Meskipun seperti kata Kevin, menulis ke file dari berbagai proses sekaligus adalah sesuatu yang ingin Anda hindari jika memungkinkan.

Jika Anda dapat memperbaiki masalah Anda ke dalam database, Anda bisa menggunakan SQLite. Ini mendukung akses bersamaan dan menangani pengunciannya sendiri.

John Fouhy
sumber
16
+1 - SQLite hampir selalu merupakan cara untuk masuk dalam situasi seperti ini.
cdleary
2
Portalocker memerlukan Ekstensi Python untuk Windows, untuk itu.
n611x007
2
@naxa ada varian yang hanya bergantung pada msvcrt dan ctypes, lihat roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/…
Shmil The Cat
@ n611x007 Portalocker baru saja diperbarui sehingga tidak memerlukan ekstensi pada Windows lagi :)
Wolph
2
SQLite mendukung akses bersamaan?
piotr
23

Solusi lain mengutip banyak basis kode eksternal. Jika Anda lebih suka melakukannya sendiri, berikut adalah beberapa kode untuk solusi lintas-platform yang menggunakan masing-masing alat penguncian file pada sistem Linux / DOS.

try:
    # Posix based file locking (Linux, Ubuntu, MacOS, etc.)
    import fcntl, os
    def lock_file(f):
        fcntl.lockf(f, fcntl.LOCK_EX)
    def unlock_file(f):
        fcntl.lockf(f, fcntl.LOCK_UN)
except ModuleNotFoundError:
    # Windows file locking
    import msvcrt, os
    def file_size(f):
        return os.path.getsize( os.path.realpath(f.name) )
    def lock_file(f):
        msvcrt.locking(f.fileno(), msvcrt.LK_RLCK, file_size(f))
    def unlock_file(f):
        msvcrt.locking(f.fileno(), msvcrt.LK_UNLCK, file_size(f))


# Class for ensuring that all file operations are atomic, treat
# initialization like a standard call to 'open' that happens to be atomic.
# This file opener *must* be used in a "with" block.
class AtomicOpen:
    # Open the file with arguments provided by user. Then acquire
    # a lock on that file object (WARNING: Advisory locking).
    def __init__(self, path, *args, **kwargs):
        # Open the file and acquire a lock on the file before operating
        self.file = open(path,*args, **kwargs)
        # Lock the opened file
        lock_file(self.file)

    # Return the opened file object (knowing a lock has been obtained).
    def __enter__(self, *args, **kwargs): return self.file

    # Unlock the file and close the file object.
    def __exit__(self, exc_type=None, exc_value=None, traceback=None):        
        # Flush to make sure all buffered contents are written to file.
        self.file.flush()
        os.fsync(self.file.fileno())
        # Release the lock on the file.
        unlock_file(self.file)
        self.file.close()
        # Handle exceptions that may have come up during execution, by
        # default any exceptions are raised to the user.
        if (exc_type != None): return False
        else:                  return True        

Sekarang, AtomicOpendapat digunakan di withblok di mana orang biasanya menggunakan openpernyataan.

PERINGATAN: Jika berjalan pada Windows dan Python crash sebelum keluar dipanggil, saya tidak yakin apa perilaku kunci itu.

PERINGATAN: Penguncian yang disediakan di sini adalah saran, bukan mutlak. Semua proses yang berpotensi bersaing harus menggunakan kelas "AtomicOpen".

Thomas Lux
sumber
unlock_fileFile di linux tidak boleh menelepon fcntllagi dengan LOCK_UNbendera?
eadmaster
Buka kunci secara otomatis terjadi ketika objek file ditutup. Namun, itu praktik pemrograman yang buruk bagi saya untuk tidak memasukkannya. Saya telah memperbarui kode dan menambahkan operasi buka kunci fcntl!
Thomas Lux
Di dalam __exit__kamu di closeluar kunci setelah unlock_file. Saya percaya runtime bisa menyiram (yaitu, menulis) data selama close. Saya percaya seseorang harus flushdan di fsyncbawah kunci untuk memastikan tidak ada data tambahan yang ditulis di luar kunci selama close.
Benjamin Bannier
Terima kasih atas koreksinya! Aku diverifikasi bahwa tidak adalah kemungkinan untuk kondisi lomba tanpa flushdan fsync. Saya telah menambahkan dua baris yang Anda sarankan sebelum menelepon unlock. Saya diuji ulang dan kondisi balapan tampaknya teratasi.
Thomas Lux
1
Satu-satunya hal yang akan "salah" adalah bahwa pada saat proses 1 mengunci file isinya akan terpotong (konten dihapus). Anda dapat mengujinya sendiri dengan menambahkan file lain "terbuka" dengan huruf "w" pada kode di atas sebelum kunci. Ini tidak dapat dihindari, karena Anda harus membuka file sebelum menguncinya. Untuk memperjelas, "atomik" dalam arti bahwa hanya konten file yang sah akan ditemukan dalam file. Ini berarti bahwa Anda tidak akan pernah mendapatkan file dengan konten dari beberapa proses yang bersaing dicampur bersama.
Thomas Lux
15

Saya lebih suka lockfile - Penguncian file yang bebas platform

ferrdo
sumber
3
Pustaka ini tampaknya ditulis dengan baik, tetapi tidak ada mekanisme untuk mendeteksi file kunci basi. Ini melacak PID yang menciptakan kunci, jadi harus memungkinkan untuk mengetahui apakah proses itu masih berjalan.
sherbang
1
@sherbang: bagaimana dengan remove_existing_pidfile ?
Janus Troelsen
@ JanusTroelsen modul pidlockfile tidak memperoleh kunci secara atomis.
sherbang
@sherbang Anda yakin? Ini membuka file kunci dengan mode O_CREAT | O_EXCL.
mhsmith
2
Harap dicatat bahwa perpustakaan ini telah digantikan dan merupakan bagian dari github.com/harlowja/fasteners
congusbongus
13

Saya telah mencari beberapa solusi untuk melakukan itu dan pilihan saya adalah oslo.concurrency

Ini kuat dan relatif terdokumentasi dengan baik. Ini didasarkan pada pengencang.

Solusi lain:

  • Portalocker : membutuhkan pywin32, yang merupakan instalasi exe, jadi tidak mungkin melalui pip
  • pengencang : didokumentasikan dengan buruk
  • lockfile : usang
  • flufl.lock : Penguncian file aman-NFS untuk sistem POSIX.
  • simpleflock : Pembaruan terakhir 2013-07
  • zc.lockfile : Pembaruan terakhir 2016-06 (mulai 2017-03)
  • lock_file : Pembaruan terakhir pada 2007-10
Maxime Viargues
sumber
re: Portalocker, sekarang Anda dapat menginstal pywin32 melalui pip melalui paket pypiwin32.
Timothy Jannace
13

Mengunci adalah platform dan perangkat khusus, tetapi secara umum, Anda memiliki beberapa opsi:

  1. Gunakan flock (), atau setara (jika os Anda mendukungnya). Ini adalah penguncian penasihat, kecuali jika Anda memeriksa kuncinya, diabaikan.
  2. Gunakan metodologi lock-copy-move-unlock, di mana Anda menyalin file, menulis data baru, kemudian memindahkannya (pindah, bukan salin - pindah adalah operasi atom di Linux - periksa OS Anda), dan Anda memeriksa keberadaan file kunci.
  3. Gunakan direktori sebagai "kunci". Ini diperlukan jika Anda menulis ke NFS, karena NFS tidak mendukung flock ().
  4. Ada juga kemungkinan menggunakan memori bersama antara proses, tetapi saya belum pernah mencobanya; ini sangat spesifik untuk OS.

Untuk semua metode ini, Anda harus menggunakan teknik spin-lock (coba-setelah-kegagalan) untuk mendapatkan dan menguji kunci. Ini meninggalkan jendela kecil untuk kesalahan sinkronisasi, tetapi umumnya cukup kecil untuk tidak menjadi masalah besar.

Jika Anda mencari solusi yang lintas platform, maka Anda lebih baik masuk ke sistem lain melalui beberapa mekanisme lain (hal terbaik berikutnya adalah teknik NFS di atas).

Perhatikan bahwa sqlite tunduk pada batasan yang sama pada NFS dengan file normal, jadi Anda tidak dapat menulis ke database sqlite pada jaringan berbagi dan mendapatkan sinkronisasi secara gratis.

Richard Levasseur
sumber
4
Catatan: Pindah / Ganti nama bukan atom di Win32. Referensi: stackoverflow.com/questions/167414/…
sherbang
4
Catatan baru: os.renamesekarang menjadi atom di Win32 sejak Python 3.3: bugs.python.org/issue8828
Ghostkeeper
7

Mengoordinasikan akses ke satu file pada level OS penuh dengan segala macam masalah yang mungkin tidak ingin Anda selesaikan.

Taruhan terbaik Anda adalah memiliki proses terpisah yang mengoordinasikan akses baca / tulis ke file itu.

Kevin
sumber
19
"proses terpisah yang mengoordinasikan akses baca / tulis ke file itu" - dengan kata lain, mengimplementasikan server database :-)
Eli Bendersky
1
Ini sebenarnya jawaban terbaik. Untuk hanya mengatakan "gunakan server database" terlalu disederhanakan, karena db tidak selalu akan menjadi alat yang tepat untuk pekerjaan itu. Bagaimana jika perlu file teks biasa? Solusi yang baik mungkin untuk menelurkan proses anak dan kemudian mengaksesnya melalui pipa bernama, soket unix, atau memori bersama.
Brendon Crawford
9
-1 karena ini hanya FUD tanpa penjelasan. Mengunci file untuk menulis sepertinya konsep yang cukup mudah bagi saya bahwa OS menawarkan dengan fungsi seperti flockuntuk itu. Pendekatan "gulung mutex Anda sendiri dan proses daemon untuk mengelolanya" tampaknya merupakan pendekatan yang agak ekstrem dan rumit untuk menyelesaikan ... masalah yang belum Anda sampaikan kepada kami, tetapi hanya menyarankan untuk mencari yang ada.
Mark Amery
-1 untuk alasan yang diberikan oleh @Mark Amery, serta untuk menawarkan pendapat yang tidak berdasar tentang masalah mana yang ingin dipecahkan OP
Michael Krebs
5

Mengunci file biasanya operasi platform-spesifik, jadi Anda mungkin perlu untuk memungkinkan kemungkinan berjalan pada sistem operasi yang berbeda. Sebagai contoh:

import os

def my_lock(f):
    if os.name == "posix":
        # Unix or OS X specific locking here
    elif os.name == "nt":
        # Windows specific locking here
    else:
        print "Unknown operating system, lock unavailable"
Greg Hewgill
sumber
7
Anda mungkin sudah mengetahui hal ini, tetapi modul platform juga tersedia untuk mendapatkan informasi tentang platform yang sedang berjalan. platform.system (). docs.python.org/library/platform.html .
monkut
2

Saya telah bekerja pada situasi seperti ini di mana saya menjalankan banyak salinan dari program yang sama dari dalam direktori / folder yang sama dan kesalahan logging. Pendekatan saya adalah menulis "file kunci" ke disk sebelum membuka file log. Program memeriksa keberadaan "file kunci" sebelum melanjutkan, dan menunggu gilirannya jika "file kunci" ada.

Ini kodenya:

def errlogger(error):

    while True:
        if not exists('errloglock'):
            lock = open('errloglock', 'w')
            if exists('errorlog'): log = open('errorlog', 'a')
            else: log = open('errorlog', 'w')
            log.write(str(datetime.utcnow())[0:-7] + ' ' + error + '\n')
            log.close()
            remove('errloglock')
            return
        else:
            check = stat('errloglock')
            if time() - check.st_ctime > 0.01: remove('errloglock')
            print('waiting my turn')

Sunting --- Setelah memikirkan beberapa komentar tentang kunci basi di atas saya mengedit kode untuk menambahkan cek untuk staleness dari "file kunci." Waktu beberapa ribu iterasi fungsi ini pada sistem saya memberi dan rata-rata 0,002066 ... detik dari sebelum:

lock = open('errloglock', 'w')

hanya setelah:

remove('errloglock')

jadi saya pikir saya akan mulai dengan 5 kali jumlah itu untuk menunjukkan staleness dan memonitor situasi untuk masalah.

Juga, ketika saya sedang mengerjakan pengaturan waktu, saya menyadari bahwa saya memiliki sedikit kode yang tidak terlalu diperlukan:

lock.close()

yang saya segera ikuti pernyataan terbuka, jadi saya telah menghapusnya di edit ini.

kumis putih
sumber
2

Untuk menambahkan jawaban Evan Fossmark , berikut adalah contoh cara menggunakan filelock :

from filelock import FileLock

lockfile = r"c:\scr.txt"
lock = FileLock(lockfile + ".lock")
with lock:
    file = open(path, "w")
    file.write("123")
    file.close()

Kode apa pun di dalam with lock:blok adalah thread-safe, artinya kode itu akan selesai sebelum proses lain memiliki akses ke file.

Josh Correia
sumber
1

The skenario seperti itu: Pengguna meminta file untuk melakukan sesuatu. Kemudian, jika pengguna mengirim permintaan yang sama lagi, itu memberi tahu pengguna bahwa permintaan kedua tidak dilakukan sampai permintaan pertama selesai. Itu sebabnya, saya menggunakan mekanisme kunci untuk menangani masalah ini.

Ini kode kerja saya:

from lockfile import LockFile
lock = LockFile(lock_file_path)
status = ""
if not lock.is_locked():
    lock.acquire()
    status = lock.path + ' is locked.'
    print status
else:
    status = lock.path + " is already locked."
    print status

return status
Günay Gültekin
sumber
0

Saya menemukan implementasi (!) Yang sederhana dan berhasil dari grizzled-python.

Penggunaan sederhana os.open (..., O_EXCL) + os.close () tidak berfungsi di windows.

Bicara
sumber
4
Opsi O_EXCL tidak terkait dengan kunci
Sergei
0

Anda mungkin menemukan pylocker sangat berguna. Ini dapat digunakan untuk mengunci file atau untuk mekanisme penguncian secara umum dan dapat diakses dari beberapa proses Python sekaligus.

Jika Anda hanya ingin mengunci file, inilah cara kerjanya:

import uuid
from pylocker import Locker

#  create a unique lock pass. This can be any string.
lpass = str(uuid.uuid1())

# create locker instance.
FL = Locker(filePath='myfile.txt', lockPass=lpass, mode='w')

# aquire the lock
with FL as r:
    # get the result
    acquired, code, fd  = r

    # check if aquired.
    if fd is not None:
        print fd
        fd.write("I have succesfuly aquired the lock !")

# no need to release anything or to close the file descriptor, 
# with statement takes care of that. let's print fd and verify that.
print fd
Cobry
sumber