ฉันต้องล็อคไฟล์เพื่อเขียนเป็น Python มันจะสามารถเข้าถึงได้จากกระบวนการ Python หลายรายการพร้อมกัน ฉันพบวิธีแก้ไขปัญหาบางอย่างทางออนไลน์ แต่ส่วนใหญ่ล้มเหลวสำหรับวัตถุประสงค์ของฉันเนื่องจากมักใช้กับ Unix based หรือ Windows เท่านั้น
ฉันต้องล็อคไฟล์เพื่อเขียนเป็น Python มันจะสามารถเข้าถึงได้จากกระบวนการ Python หลายรายการพร้อมกัน ฉันพบวิธีแก้ไขปัญหาบางอย่างทางออนไลน์ แต่ส่วนใหญ่ล้มเหลวสำหรับวัตถุประสงค์ของฉันเนื่องจากมักใช้กับ Unix based หรือ Windows เท่านั้น
คำตอบ:
เอาล่ะฉันก็เลยจบลงด้วยรหัสที่ฉันเขียนที่นี่ในลิงค์เว็บไซต์ของฉันตายไปแล้วดูที่ 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
มีโมดูลการล็อกไฟล์ข้ามแพลตฟอร์มที่นี่: Portalocker
แม้ว่าเควินจะพูดว่าการเขียนไฟล์จากหลาย ๆ กระบวนการพร้อมกันเป็นสิ่งที่คุณต้องการหลีกเลี่ยงถ้าเป็นไปได้
หากคุณสามารถใส่ปัญหาของคุณลงในฐานข้อมูลคุณสามารถใช้ SQLite รองรับการเข้าถึงพร้อมกันและจัดการการล็อคของตัวเอง
โซลูชันอื่นอ้างถึงฐานรหัสภายนอกจำนวนมาก หากคุณต้องการทำด้วยตัวเองนี่คือรหัสสำหรับโซลูชันข้ามแพลตฟอร์มที่ใช้เครื่องมือล็อกไฟล์ตามลำดับบนระบบ 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
แฟล็กอีกครั้ง?
__exit__
คุณนอกล็อคหลังจากclose
unlock_file
ผมเชื่อว่ารันไทม์สามารถล้าง (เช่นเขียน) close
ข้อมูลระหว่างการ ผมเชื่อว่าหนึ่งต้องflush
และภายใต้การล็อคเพื่อให้แน่ใจว่าไม่มีข้อมูลเพิ่มเติมเป็นลายลักษณ์อักษรนอกล็อคในช่วงfsync
close
flush
ฉันได้เพิ่มสองบรรทัดที่คุณแนะนำก่อนที่จะเรียกfsync
unlock
ฉันได้รับการทดสอบอีกครั้งและสภาพการแข่งขันดูเหมือนจะได้รับการแก้ไข
ฉันชอบlockfile - การล็อคไฟล์ที่ไม่ขึ้นกับแพลตฟอร์ม
ฉันได้ดูวิธีแก้ปัญหาต่าง ๆ เพื่อทำเช่นนั้นและตัวเลือกของฉันได้รับ oslo.concurrency
มันทรงพลังและบันทึกไว้ค่อนข้างดี มันขึ้นอยู่กับตัวยึด
โซลูชั่นอื่น ๆ :
การล็อคเป็นแพลตฟอร์มและอุปกรณ์เฉพาะ แต่โดยทั่วไปคุณมีตัวเลือกน้อย:
สำหรับวิธีการทั้งหมดเหล่านี้คุณจะต้องใช้เทคนิคการหมุน - ล็อค (ลองอีกครั้งหลังจากความล้มเหลว) สำหรับการรับและทดสอบการล็อค การทำเช่นนี้ทำให้หน้าต่างเล็ก ๆ สำหรับการซิงโครไนซ์ผิดพลาด แต่โดยทั่วไปแล้วมันเล็กพอที่จะไม่ใช่ปัญหาใหญ่
หากคุณกำลังมองหาวิธีแก้ปัญหาที่เป็น cross platform คุณควรเข้าสู่ระบบอื่นผ่านทางกลไกอื่น ๆ (สิ่งที่ดีที่สุดถัดไปคือเทคนิค NFS ด้านบน)
โปรดทราบว่า sqlite อยู่ภายใต้ข้อ จำกัด เดียวกันกับ NFS ที่ไฟล์ปกติเป็นดังนั้นคุณไม่สามารถเขียนไปยังฐานข้อมูล sqlite บนเครือข่ายที่ใช้ร่วมกันและรับการซิงโครไนซ์ได้ฟรี
os.rename
ขณะนี้มีอะตอมใน Win32 ตั้งแต่ Python 3.3: bugs.python.org/issue8828
การประสานงานการเข้าถึงไฟล์เดียวที่ระดับ OS นั้นเต็มไปด้วยปัญหาทุกประเภทที่คุณอาจไม่ต้องการแก้ไข
ทางออกที่ดีที่สุดของคุณคือมีกระบวนการแยกต่างหากที่ประสานการเข้าถึงการอ่าน / เขียนไฟล์นั้น
flock
นั้น วิธีการ "ม้วน mutexes ของคุณเองและกระบวนการ daemon เพื่อจัดการพวกเขา" ดูเหมือนจะเป็นวิธีที่ค่อนข้างซับซ้อนและซับซ้อนในการแก้ไข ... ปัญหาที่คุณยังไม่ได้บอกเราจริง ๆ
การล็อกไฟล์มักเป็นการดำเนินงานเฉพาะแพลตฟอร์มดังนั้นคุณอาจต้องอนุญาตให้มีความเป็นไปได้ในการใช้งานบนระบบปฏิบัติการที่แตกต่างกัน ตัวอย่างเช่น:
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"
ฉันทำงานกับสถานการณ์เช่นนี้ซึ่งฉันเรียกใช้โปรแกรมเดียวกันหลายชุดจากในไดเรกทอรี / โฟลเดอร์เดียวกันและข้อผิดพลาดในการบันทึก แนวทางของฉันคือการเขียน "ล็อกไฟล์" ลงในแผ่นดิสก์ก่อนเปิดไฟล์บันทึก โปรแกรมจะตรวจสอบว่ามี "ล็อคไฟล์" ก่อนที่จะดำเนินการต่อและรอการเปิดหากมี "ล็อคไฟล์" อยู่
นี่คือรหัส:
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()
ซึ่งฉันทำตามคำสั่งเปิดทันทีดังนั้นฉันจึงลบมันในการแก้ไขนี้
เพิ่มในคำตอบอีวาน 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:
บล็อกนั้นปลอดภัยสำหรับเธรดหมายความว่ามันจะเสร็จสิ้นก่อนที่กระบวนการอื่นจะสามารถเข้าถึงไฟล์ได้
สถานการณ์เป็นเช่นนั้น: ผู้ใช้ขอไฟล์ที่จะทำบางสิ่งบางอย่าง จากนั้นหากผู้ใช้ส่งการร้องขอเดียวกันอีกครั้งผู้ใช้จะแจ้งให้ผู้ใช้ทราบว่าการร้องขอที่สองไม่ได้ดำเนินการจนกว่าการร้องขอแรกจะเสร็จสิ้น นั่นเป็นเหตุผลที่ฉันใช้กลไกการล็อคเพื่อจัดการปัญหานี้
นี่คือรหัสการทำงานของฉัน:
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
ผมพบว่าการที่ง่ายและทำงาน (!) การดำเนินงานจากผมหงอกหลาม
ใช้งานง่าย os.open (... , O_EXCL) + os.close () ไม่ทำงานบน windows
คุณอาจพบ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