การล็อคไฟล์ใน Python


152

ฉันต้องล็อคไฟล์เพื่อเขียนเป็น Python มันจะสามารถเข้าถึงได้จากกระบวนการ Python หลายรายการพร้อมกัน ฉันพบวิธีแก้ไขปัญหาบางอย่างทางออนไลน์ แต่ส่วนใหญ่ล้มเหลวสำหรับวัตถุประสงค์ของฉันเนื่องจากมักใช้กับ Unix based หรือ Windows เท่านั้น

คำตอบ:


115

เอาล่ะฉันก็เลยจบลงด้วยรหัสที่ฉันเขียนที่นี่ในลิงค์เว็บไซต์ของฉันตายไปแล้วดูที่ archive.org ( มีอยู่ใน GitHub ด้วย ) ฉันสามารถใช้มันในแบบต่อไปนี้:

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
ตามที่ระบุไว้โดยความคิดเห็นที่โพสต์บล็อกวิธีนี้ไม่ได้ "สมบูรณ์แบบ" ซึ่งเป็นไปได้สำหรับโปรแกรมที่จะยุติในลักษณะที่ล็อคถูกทิ้งไว้และคุณต้องลบล็อคด้วยตนเองก่อนที่ไฟล์ เข้าถึงได้อีกครั้ง อย่างไรก็ตามนอกเหนือจากนี้ยังคงเป็นทางออกที่ดี
leetNightshade

3
ยังมีรุ่นที่ได้รับการปรับปรุงอีกรุ่นหนึ่งของ Evan FileLock ที่นี่: github.com/ilastik/lazyflow/blob/master/lazyflow/utility/ ......
Stuart Berg

3
OpenStack ได้เผยแพร่การใช้งานของตนเอง (อย่างดี, ข้าม Montanaro) - pylockfile - คล้ายกับที่กล่าวถึงในความคิดเห็นก่อนหน้านี้ แต่ก็คุ้มค่าที่จะดู
jweyrich

7
@jweyrich Openstacks pylockfile เลิกใช้แล้ว ขอแนะนำให้ใช้รัดหรือoslo.concurrencyแทน
harbun

2
การใช้งานที่คล้ายกันอีกอย่างฉันเดาว่า: github.com/benediktschmitt/py-filelock
herry

39

มีโมดูลการล็อกไฟล์ข้ามแพลตฟอร์มที่นี่: Portalocker

แม้ว่าเควินจะพูดว่าการเขียนไฟล์จากหลาย ๆ กระบวนการพร้อมกันเป็นสิ่งที่คุณต้องการหลีกเลี่ยงถ้าเป็นไปได้

หากคุณสามารถใส่ปัญหาของคุณลงในฐานข้อมูลคุณสามารถใช้ SQLite รองรับการเข้าถึงพร้อมกันและจัดการการล็อคของตัวเอง


16
+1 - SQLite มักจะเป็นวิธีที่จะไปในสถานการณ์เหล่านี้
cdleary

2
Portalocker ต้องการ Python Extensions สำหรับ Windows
n611x007

2
@naxa มีความแตกต่างของมันซึ่งอาศัยเฉพาะใน Msvcrt และ ctypes โปรดดูroundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/...
Shmil แมว

@ n611x007 Portalocker เพิ่งได้รับการปรับปรุงเพื่อให้มันไม่ต้องใช้ส่วนขยายใด ๆ บน Windows อีกต่อไป :)
Wolph

2
SQLite รองรับการเข้าถึงพร้อมกันไหม?
piotr

23

โซลูชันอื่นอ้างถึงฐานรหัสภายนอกจำนวนมาก หากคุณต้องการทำด้วยตัวเองนี่คือรหัสสำหรับโซลูชันข้ามแพลตฟอร์มที่ใช้เครื่องมือล็อกไฟล์ตามลำดับบนระบบ 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        

ตอนนี้AtomicOpenสามารถใช้ในwithบล็อกที่ปกติจะใช้openคำสั่ง

คำเตือน:หากทำงานบน Windows และ Python ขัดข้องก่อนออกจากการเรียกฉันไม่แน่ใจว่าลักษณะการล็อคจะเป็นอย่างไร

คำเตือน:การล็อคที่ให้ไว้ที่นี่เป็นคำแนะนำไม่ใช่อย่างสมบูรณ์ กระบวนการที่อาจแข่งขันทั้งหมดต้องใช้คลาส "AtomicOpen"


unlock_fileไฟล์บน linux ไม่ควรโทรfcntlด้วยLOCK_UNแฟล็กอีกครั้ง?
eadmaster

การปลดล็อคจะเกิดขึ้นโดยอัตโนมัติเมื่อวัตถุไฟล์ถูกปิด อย่างไรก็ตามมันเป็นวิธีการเขียนโปรแกรมที่ไม่ดีของฉันที่จะไม่รวมมัน ฉันอัปเดตรหัสแล้วเพิ่มการดำเนินการปลดล็อค fcntl!
โทมัสลักซ์

ใน__exit__คุณนอกล็อคหลังจากclose unlock_fileผมเชื่อว่ารันไทม์สามารถล้าง (เช่นเขียน) closeข้อมูลระหว่างการ ผมเชื่อว่าหนึ่งต้องflushและภายใต้การล็อคเพื่อให้แน่ใจว่าไม่มีข้อมูลเพิ่มเติมเป็นลายลักษณ์อักษรนอกล็อคในช่วงfsync close
Benjamin Bannier

ขอบคุณสำหรับการแก้ไข! ผมสอบว่ามีเป็นไปได้สำหรับสภาพการแข่งขันโดยไม่ได้และflush ฉันได้เพิ่มสองบรรทัดที่คุณแนะนำก่อนที่จะเรียกfsync unlockฉันได้รับการทดสอบอีกครั้งและสภาพการแข่งขันดูเหมือนจะได้รับการแก้ไข
โทมั

1
สิ่งเดียวที่จะไป "ผิด" คือเมื่อถึงเวลา 1 กระบวนการล็อกไฟล์เนื้อหาจะถูกตัดทอน (เนื้อหาถูกลบ) คุณสามารถทดสอบได้ด้วยตัวเองโดยเพิ่มไฟล์ "open" อีกหนึ่งตัวด้วย "w" ลงในรหัสด้านบนก่อนล็อค สิ่งนี้หลีกเลี่ยงไม่ได้เนื่องจากคุณต้องเปิดไฟล์ก่อนที่จะล็อค ในการชี้แจง "อะตอมมิก" มีความหมายว่าจะพบเฉพาะเนื้อหาไฟล์ที่ถูกกฎหมายเท่านั้นในไฟล์ ซึ่งหมายความว่าคุณจะไม่ได้รับไฟล์ที่มีเนื้อหาจากกระบวนการที่แข่งขันกันหลายอย่างรวมกัน
โทมั

15

ฉันชอบlockfile - การล็อคไฟล์ที่ไม่ขึ้นกับแพลตฟอร์ม


3
ห้องสมุดนี้ดูเหมือนจะเขียนได้ดี แต่ไม่มีกลไกในการตรวจจับไฟล์ล็อคเก่า มันติดตาม PID ที่สร้างการล็อคดังนั้นควรเป็นไปได้ที่จะบอกว่ากระบวนการนั้นยังคงทำงานอยู่หรือไม่
sherbang

1
@sherbang: สิ่งที่เกี่ยวกับremove_existing_pidfile ?
Janus Troelsen

@JanusTroelsen โมดูล pidlockfile ไม่ได้รับการล็อคแบบอะตอม
sherbang

@sherbang คุณแน่ใจหรือไม่ มันเปิดไฟล์ล็อคด้วยโหมด O_CREAT | O_EXCL
mhsmith

2
โปรดทราบว่า
ไลบรารี่

13

ฉันได้ดูวิธีแก้ปัญหาต่าง ๆ เพื่อทำเช่นนั้นและตัวเลือกของฉันได้รับ oslo.concurrency

มันทรงพลังและบันทึกไว้ค่อนข้างดี มันขึ้นอยู่กับตัวยึด

โซลูชั่นอื่น ๆ :

  • Portalocker : ต้องการ pywin32 ซึ่งเป็นการติดตั้ง exe ดังนั้นจึงไม่สามารถทำได้ผ่าน pip
  • รัด : เอกสารไม่ดี
  • lockfile : เลิกใช้แล้ว
  • flufl.lock : การล็อคไฟล์NFS-ปลอดภัยสำหรับระบบ POSIX
  • simpleflock : อัปเดตล่าสุด 2013-07
  • zc.lockfile : อัพเดทล่าสุด 2016-06 (ณ วันที่ 2017-03)
  • lock_file : อัพเดทล่าสุดในปี 2550-2553

เรื่อง: Portalocker ตอนนี้คุณสามารถติดตั้ง pywin32 ผ่าน pip ผ่านแพ็คเกจ pypiwin32
Timothy Jannace


13

การล็อคเป็นแพลตฟอร์มและอุปกรณ์เฉพาะ แต่โดยทั่วไปคุณมีตัวเลือกน้อย:

  1. ใช้ flock () หรือเทียบเท่า (หากระบบปฏิบัติการของคุณรองรับ) นี่คือการล็อคคำแนะนำยกเว้นว่าคุณตรวจสอบการล็อค
  2. ใช้วิธีการล็อค - คัดลอก - ย้าย - ปลดล็อคที่คุณคัดลอกไฟล์เขียนข้อมูลใหม่แล้วย้าย (ย้ายไม่คัดลอก - ย้ายเป็นการดำเนินการปรมาณูใน Linux - ตรวจสอบระบบปฏิบัติการของคุณ) และคุณตรวจสอบ มีไฟล์ล็อคอยู่
  3. ใช้ไดเรกทอรีเป็น "ล็อค" นี่เป็นสิ่งจำเป็นหากคุณเขียนถึง NFS เนื่องจาก NFS ไม่สนับสนุนฝูง ()
  4. นอกจากนี้ยังมีความเป็นไปได้ที่จะใช้หน่วยความจำที่ใช้ร่วมกันระหว่างกระบวนการ แต่ฉันไม่เคยลองมาก่อน มันเฉพาะระบบปฏิบัติการมาก

สำหรับวิธีการทั้งหมดเหล่านี้คุณจะต้องใช้เทคนิคการหมุน - ล็อค (ลองอีกครั้งหลังจากความล้มเหลว) สำหรับการรับและทดสอบการล็อค การทำเช่นนี้ทำให้หน้าต่างเล็ก ๆ สำหรับการซิงโครไนซ์ผิดพลาด แต่โดยทั่วไปแล้วมันเล็กพอที่จะไม่ใช่ปัญหาใหญ่

หากคุณกำลังมองหาวิธีแก้ปัญหาที่เป็น cross platform คุณควรเข้าสู่ระบบอื่นผ่านทางกลไกอื่น ๆ (สิ่งที่ดีที่สุดถัดไปคือเทคนิค NFS ด้านบน)

โปรดทราบว่า sqlite อยู่ภายใต้ข้อ จำกัด เดียวกันกับ NFS ที่ไฟล์ปกติเป็นดังนั้นคุณไม่สามารถเขียนไปยังฐานข้อมูล sqlite บนเครือข่ายที่ใช้ร่วมกันและรับการซิงโครไนซ์ได้ฟรี


4
หมายเหตุ: Move / Rename ไม่ใช่ atomic ใน Win32 อ้างอิง: stackoverflow.com/questions/167414/…
sherbang

4
หมายเหตุใหม่: os.renameขณะนี้มีอะตอมใน Win32 ตั้งแต่ Python 3.3: bugs.python.org/issue8828
Ghostkeeper

7

การประสานงานการเข้าถึงไฟล์เดียวที่ระดับ OS นั้นเต็มไปด้วยปัญหาทุกประเภทที่คุณอาจไม่ต้องการแก้ไข

ทางออกที่ดีที่สุดของคุณคือมีกระบวนการแยกต่างหากที่ประสานการเข้าถึงการอ่าน / เขียนไฟล์นั้น


19
"กระบวนการที่แยกต่างหากที่พิกัดในการอ่าน / เขียนเข้าถึงแฟ้มที่" - ในคำอื่น ๆ ที่ใช้เซิร์ฟเวอร์ฐานข้อมูล :-)
Eli Bendersky

1
นี่เป็นคำตอบที่ดีที่สุด เพียงแค่พูดว่า "ใช้เซิร์ฟเวอร์ฐานข้อมูล" นั้นง่ายเกินไปเนื่องจาก db ไม่ได้เป็นเครื่องมือที่เหมาะสมสำหรับงานเสมอไป ถ้าหากมันต้องเป็นไฟล์ข้อความธรรมดา? วิธีแก้ปัญหาที่ดีคือการวางกระบวนการเด็กแล้วเข้าถึงผ่านทางท่อที่มีชื่อซ็อกเก็ตยูนิกซ์หรือหน่วยความจำที่ใช้ร่วมกัน
Brendon Crawford

9
-1 เพราะนี่เป็นเพียง FUD โดยไม่มีคำอธิบาย การล็อกไฟล์เพื่อเขียนดูเหมือนเป็นแนวคิดที่ตรงไปตรงมาสำหรับฉันที่ OSes เสนอให้พร้อมกับฟังก์ชั่นเช่นflockนั้น วิธีการ "ม้วน mutexes ของคุณเองและกระบวนการ daemon เพื่อจัดการพวกเขา" ดูเหมือนจะเป็นวิธีที่ค่อนข้างซับซ้อนและซับซ้อนในการแก้ไข ... ปัญหาที่คุณยังไม่ได้บอกเราจริง ๆ
Mark Amery

-1 ด้วยเหตุผลที่ได้รับจาก @Mark Amery เช่นเดียวกับการเสนอความคิดเห็นที่ไม่พร้อมเพรียงกันเกี่ยวกับประเด็นที่ OP ต้องการแก้ไข
Michael Krebs

5

การล็อกไฟล์มักเป็นการดำเนินงานเฉพาะแพลตฟอร์มดังนั้นคุณอาจต้องอนุญาตให้มีความเป็นไปได้ในการใช้งานบนระบบปฏิบัติการที่แตกต่างกัน ตัวอย่างเช่น:

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
คุณอาจทราบดีอยู่แล้ว แต่โมดูลแพลตฟอร์มยังมีให้เพื่อรับข้อมูลเกี่ยวกับแพลตฟอร์มที่กำลังทำงานอยู่ platform.system () docs.python.org/library/platform.html
monkut

2

ฉันทำงานกับสถานการณ์เช่นนี้ซึ่งฉันเรียกใช้โปรแกรมเดียวกันหลายชุดจากในไดเรกทอรี / โฟลเดอร์เดียวกันและข้อผิดพลาดในการบันทึก แนวทางของฉันคือการเขียน "ล็อกไฟล์" ลงในแผ่นดิสก์ก่อนเปิดไฟล์บันทึก โปรแกรมจะตรวจสอบว่ามี "ล็อคไฟล์" ก่อนที่จะดำเนินการต่อและรอการเปิดหากมี "ล็อคไฟล์" อยู่

นี่คือรหัส:

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

แก้ไข --- หลังจากคิดถึงความคิดเห็นบางส่วนเกี่ยวกับการล็อคเก่า ๆ แล้วฉันแก้ไขรหัสเพื่อเพิ่มการตรวจสอบความไม่มีเสถียรภาพของ "ล็อกไฟล์" กำหนดเวลาการทำซ้ำหลายพันฟังก์ชั่นนี้ในระบบของฉันและให้เฉลี่ย 0.002066 ... วินาทีจากก่อนหน้านี้:

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

หลังจาก:

remove('errloglock')

ดังนั้นฉันคิดว่าฉันจะเริ่มต้นด้วย 5 เท่าของจำนวนนั้นเพื่อบ่งบอกความไม่มั่นคงและติดตามสถานการณ์ของปัญหา

นอกจากนี้ในขณะที่ฉันทำงานกับเวลาฉันรู้ว่าฉันมีรหัสเล็กน้อยที่ไม่จำเป็นจริงๆ:

lock.close()

ซึ่งฉันทำตามคำสั่งเปิดทันทีดังนั้นฉันจึงลบมันในการแก้ไขนี้


2

เพิ่มในคำตอบอีวาน Fossmark ของที่นี่คือตัวอย่างของวิธีการใช้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:บล็อกนั้นปลอดภัยสำหรับเธรดหมายความว่ามันจะเสร็จสิ้นก่อนที่กระบวนการอื่นจะสามารถเข้าถึงไฟล์ได้


1

สถานการณ์เป็นเช่นนั้น: ผู้ใช้ขอไฟล์ที่จะทำบางสิ่งบางอย่าง จากนั้นหากผู้ใช้ส่งการร้องขอเดียวกันอีกครั้งผู้ใช้จะแจ้งให้ผู้ใช้ทราบว่าการร้องขอที่สองไม่ได้ดำเนินการจนกว่าการร้องขอแรกจะเสร็จสิ้น นั่นเป็นเหตุผลที่ฉันใช้กลไกการล็อคเพื่อจัดการปัญหานี้

นี่คือรหัสการทำงานของฉัน:

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

ผมพบว่าการที่ง่ายและทำงาน (!) การดำเนินงานจากผมหงอกหลาม

ใช้งานง่าย os.open (... , O_EXCL) + os.close () ไม่ทำงานบน windows


4
ตัวเลือก O_EXCL ไม่เกี่ยวข้องกับการล็อค
Sergei

0

คุณอาจพบpylockerมีประโยชน์มาก มันสามารถใช้เพื่อล็อคไฟล์หรือสำหรับกลไกการล็อคโดยทั่วไปและสามารถเข้าถึงได้จากกระบวนการ Python หลายรายการในครั้งเดียว

หากคุณเพียงแค่ต้องการล็อคไฟล์นี่คือวิธีการ:

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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.