Python'da bir dosyayı kilitleme


152

Python'a yazmak için bir dosyayı kilitlemem gerekiyor. Bir kerede birden fazla Python işleminden erişilebilir. Çevrimiçi olarak bazı çözümler buldum, ancak çoğu zaman yalnızca Unix tabanlı veya Windows tabanlı oldukları için çoğu zaman başarısızım.

Yanıtlar:


115

Tamam, bu yüzden burada yazdığım kodla devam ettim , web sitemde link öldü, archive.org'da ( GitHub'da da mevcut ) görüntüleyin. Aşağıdaki şekilde kullanabilirim:

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

10
Blog gönderisindeki bir yorumda belirtildiği gibi, bu çözüm "mükemmel" değildir, çünkü programın kilit yerinde kalacak şekilde sona ermesi ve dosyadan önce kilidi manuel olarak silmeniz gerekir. tekrar erişilebilir hale gelir. Ancak, bu bir yana, bu hala iyi bir çözümdür.
leetNightshade


3
OpenStack kendi (tamam, Skip Montanaro'nun) uygulamasını yayınladı - pylockfile - Önceki yorumlarda belirtilenlere çok benzer, ancak yine de bir göz atmaya değer.
jweyrich

7
@jweyrich openstacks pylockfile artık kullanımdan kaldırıldı. Bunun yerine bağlantı elemanları veya oslo.concurrency kullanılması önerilir .
harbun

2
Başka benzer uygulama I tahmin: github.com/benediktschmitt/py-filelock
herry

39

Burada bir çapraz platform dosya kilitleme modülü var: Portalocker

Kevin'in dediği gibi, bir kerede birden fazla işlemden bir dosyaya yazmak mümkünse kaçınmak istediğiniz bir şeydir.

Sorununuzu bir veritabanına bağlayabiliyorsanız SQLite kullanabilirsiniz. Eşzamanlı erişimi destekler ve kendi kilitlemesini gerçekleştirir.


16
+1 - SQLite neredeyse her zaman bu tür durumlarda gitmenin yoludur.
cdleary

2
Portalocker, Windows için Python Uzantıları gerektirir.
n611x007

2
@naxa sadece msvcrt ve ctypes'e dayanan bir varyantı var, bkz. roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/…
Shmil The Cat 15:13

@ n611x007 Portalocker daha yeni güncellendi, bu yüzden artık Windows'ta herhangi bir uzantı gerektirmiyor :)
Wolph

2
SQLite eşzamanlı erişimi destekliyor mu?
piotr

23

Diğer çözümler çok sayıda harici kod tabanından bahsediyor. Bunu kendiniz yapmayı tercih ederseniz, Linux / DOS sistemlerinde ilgili dosya kilitleme araçlarını kullanan çapraz platform çözümü için bazı kodlar.

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        

Şimdi, normalde bir ifade kullanacak bir blokta AtomicOpenkullanılabilir .withopen

UYARI: Çıkış çağrılmadan önce Windows ve Python'da çöküyorsa , kilit davranışının ne olacağından emin değilim.

UYARI: Burada sağlanan kilitleme mutlak değil tavsiye niteliğindedir. Potansiyel olarak rekabet edebilecek tüm süreçler "AtomicOpen" sınıfını kullanmalıdır.


unlock_filelinux dosya bayrak fcntlile tekrar aramak gerekir LOCK_UN?
eadmaster

Kilit açma, dosya nesnesi kapatıldığında otomatik olarak gerçekleşir. Ancak, bunu dahil etmemek benim için kötü bir programlama uygulamasıydı. Kodu güncelledim ve fcntl kilit açma işlemini ekledim!
Thomas Lux

In __exit__size closedışarıda sonra kilidin unlock_file. Çalışma zamanı sırasında verileri akıtıp (yani, yazabilir) inanıyorum close. Bir zorunluluk inanıyoruz flushve fsynckilit altında sırasında kilit dış yazılır emin hiçbir ek verileri yapmak close.
Benjamin Bannier

Düzeltme için teşekkürler! Orada doğruladıktan olduğunu olmadan bir yarış durumu için olasılık flushve fsync. Aramadan önce önerdiğiniz iki hattı ekledim unlock. Yeniden test ettim ve yarış durumu çözülmüş görünüyor.
Thomas Lux

1
"Yanlış" olacak tek şey zaman süreci 1 dosyayı kilitlemek için içeriği kesilmiş (içeriği silinir) olmasıdır. Kilitten önce yukarıdaki koda "w" ile "open" adlı başka bir dosya ekleyerek bunu kendiniz test edebilirsiniz . Bu kaçınılmazdır, çünkü dosyayı kilitlemeden önce açmanız gerekir. Açıklığa kavuşturmak için, "atom" bir dosyada sadece meşru dosya içeriğinin bulunacağı anlamındadır. Bu, bir araya getirilen birden fazla rakip işlemden içeriğe sahip bir dosyayı asla almayacağınız anlamına gelir.
Thomas Lux

15

Lockfile'ı tercih ederim - Platformdan bağımsız dosya kilitleme


3
Bu kütüphane iyi yazılmış gibi görünüyor, ancak eski kilit dosyalarını algılamak için bir mekanizma yok. Kilidi oluşturan PID'yi izler, bu nedenle bu işlemin hala devam edip etmediğini söylemek mümkün olmalıdır.
sherbang

1
@sherbang: remove_existing_pidfile hakkında ne dersiniz ?
Janus Troelsen

@JanusTroelsen pidlockfile modülü kilitleri atomik olarak almıyor.
sherbang

@sherbang Emin misin? Kilit dosyasını O_CREAT | O_EXCL moduyla açar.
mhsmith

2
Bu kütüphanenin çok sayıda yerleĢtirildiğini
congusbongus'un bir

13

Bunu yapmak için çeşitli çözümlere bakıyorum ve seçimim oslo.concurrency oldu.

Güçlü ve nispeten iyi belgelenmiş. Bağlantı elemanlarına dayanır.

Diğer çözümler:


re: Portalocker, şimdi pywin32'yi pip yoluyla pypiwin32 paketi üzerinden kurabilirsiniz.
Timothy Jannace


13

Kilitleme platforma ve cihaza özeldir, ancak genellikle birkaç seçeneğiniz vardır:

  1. Flock () veya eşdeğerini kullanın (işletim sisteminiz destekliyorsa). Kilidi kontrol etmedikçe bu göz ardı edilir.
  2. Dosyayı kopyaladığınız, yeni verileri yazdığınız, sonra taşıdığınız (taşıyın, kopyalamayın - taşıma Linux'ta atomik bir işlemdir - işletim sisteminizi kontrol edin) bir kilitle-kopyala-taşı-kilidini aç yöntemini kullanın ve kilit dosyasının varlığı.
  3. Bir dizini "kilit" olarak kullanın. NFS flock () işlevini desteklemediğinden, NFS'ye yazıyorsanız bu gereklidir.
  4. İşlemler arasında paylaşılan bellek kullanma olasılığı da vardır, ancak bunu hiç denemedim; işletim sistemine özgüdür.

Tüm bu yöntemler için, kilidi elde etmek ve test etmek için bir döndürme kilidi (arıza sonrası yeniden deneme) tekniği kullanmanız gerekir. Bu, yanlış senkronizasyon için küçük bir pencere bırakır, ancak genellikle büyük bir sorun olmayacak kadar küçüktür.

Çapraz platform olan bir çözüm arıyorsanız, başka bir sistemle başka bir sisteme giriş yapmaktan daha iyidir (bir sonraki en iyi şey yukarıdaki NFS tekniğidir).

Sqlite'ın normal dosyalardaki NFS ile aynı kısıtlamalara tabi olduğunu unutmayın, bu nedenle bir ağ paylaşımındaki sqlite veritabanına yazamaz ve ücretsiz senkronizasyon elde edemezsiniz.


4
Not: Taşı / Yeniden Adlandır, Win32'de atomik değildir. Referans: stackoverflow.com/questions/167414/…
sherbang

4
Yeni not: os.renamePython 3.3'ten beri Win32'de atomik: bugs.python.org/issue8828
Ghostkeeper

7

İşletim sistemi düzeyinde tek bir dosyaya erişimi koordine etmek, muhtemelen çözmek istemediğiniz her türlü sorunla doludur.

En iyi bahsiniz, o dosyaya okuma / yazma erişimini koordine eden ayrı bir işlemdir.


19
"bu dosyaya okuma / yazma erişimini koordine eden ayrı bir işlem" - diğer bir deyişle, bir veritabanı sunucusu uygulayın :-)
Eli Bendersky

1
Bu aslında en iyi cevap. Sadece bir db her zaman iş için doğru bir araç olmayacak gibi, "bir veritabanı sunucusu kullanın" demek için basitleştirilmiştir. Düz metin dosyası olması gerekiyorsa ne olur? Bir alt süreci ortaya çıkarmak ve daha sonra adlandırılmış bir kanal, unix soketi veya paylaşılan bellek yoluyla erişmek iyi bir çözüm olabilir.
Brendon Crawford

9
-1 çünkü bu açıklama yapmadan sadece FUD. Bir dosyayı yazmak için kilitlemek, OS'lerin flockbunun gibi işlevler sundukları için oldukça basit bir kavram gibi görünüyor . "Kendi muteksilerinizi yuvarlayın ve onları yönetmek için bir daemon süreci" yaklaşımı, çözmemiz için oldukça aşırı ve karmaşık bir yaklaşım gibi görünüyor ... aslında bize söylemediğiniz bir sorun var, ama sadece korkutucu bir şekilde var.
Mark Amery

-1Mark Amery tarafından verilen nedenlerden dolayı ve OP'nin hangi sorunları çözmek istediği konusunda dayanaksız bir fikir sunmak için
Michael Krebs

5

Bir dosyayı kilitlemek genellikle platforma özgü bir işlemdir, bu nedenle farklı işletim sistemlerinde çalışma olasılığına izin vermeniz gerekebilir. Örneğin:

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"

7
Bunu zaten biliyor olabilirsiniz, ancak platform modülü çalışan platform hakkında bilgi almak için de kullanılabilir. platform.system (). docs.python.org/library/platform.html .
monkut

2

Aynı programın birden çok kopyasını aynı dizin / klasörden ve günlük hatalarından çalıştırdığım böyle bir durum üzerinde çalışıyorum. Benim yaklaşım günlük dosyasını açmadan önce diske bir "kilit dosyası" yazmak oldu. Program devam etmeden önce "kilit dosyası" olup olmadığını kontrol eder ve "kilit dosyası" mevcutsa sırasını bekler.

İşte kod:

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')

DÜZENLEME --- Yukarıdaki eski kilitler hakkında bazı yorumlar üzerinde düşündükten sonra ben "kilit dosyası" staleness için bir kontrol eklemek için kodu düzenledi. Sistemimde bu işlevin birkaç bin yinelemesini zamanlama ve hemen önce gelen ortalama 0.002066 ... saniye verdi:

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

hemen sonra:

remove('errloglock')

bu yüzden 5 kez başlayacağımı düşündüm.

Ayrıca, zamanlama ile çalışırken, gerçekten gerekli olmayan bir kod biraz olduğunu fark ettim:

lock.close()

hangi hemen açık ifadeyi takip vardı, bu yüzden bu düzenlemede kaldırdım.


2

Üzerine eklemek için Evan Fossmark cevabı , burada nasıl kullanılacağına ilişkin bir örnek 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()

with lock:Blok içindeki herhangi bir kod iş parçacığı için güvenlidir, yani başka bir işlemin dosyaya erişmeden önce biteceği anlamına gelir.


1

senaryo böyledir: Kullanıcı şey yapmak için bir dosyasını ister. Daha sonra kullanıcı aynı isteği tekrar gönderirse, ilk istek bitene kadar ikinci isteğin yapılmadığını kullanıcıya bildirir. Bu yüzden bu sorunu çözmek için kilit mekanizması kullanıyorum.

İşte benim çalışma kodu:

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

0

Grizzled-python'dan basit ve çalışmış bir (!) Uygulama buldum .

Basit kullanım os.open (..., O_EXCL) + os.close () pencerelerde çalışmadı.


4
O_EXCL seçeneği kilitle ilgili değil
Sergei

0

Pylocker'ı çok faydalı bulabilirsiniz . Bir dosyayı kilitlemek veya genel olarak kilitleme mekanizmaları için kullanılabilir ve aynı anda birden fazla Python işleminden erişilebilir.

Bir dosyayı kilitlemek istiyorsanız şu şekilde çalışır:

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
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.