Khóa một tệp trong Python


152

Tôi cần phải khóa một tập tin để viết bằng Python. Nó sẽ được truy cập từ nhiều quá trình Python cùng một lúc. Tôi đã tìm thấy một số giải pháp trực tuyến, nhưng hầu hết đều thất bại vì mục đích của tôi vì chúng thường chỉ dựa trên Unix hoặc dựa trên Windows.

Câu trả lời:


115

Được rồi, vì vậy tôi đã kết thúc với mã tôi đã viết ở đây, trên liên kết trang web của tôi đã chết, xem trên archive.org ( cũng có sẵn trên GitHub ). Tôi có thể sử dụng nó theo cách sau:

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
Như đã lưu ý bởi một bình luận tại bài đăng trên blog, giải pháp này không "hoàn hảo", theo đó chương trình có thể chấm dứt theo cách mà khóa được đặt đúng chỗ và bạn phải xóa khóa theo cách thủ công trước khi tập tin trở nên dễ tiếp cận trở lại. Tuy nhiên, điều đó sang một bên, đây vẫn là một giải pháp tốt.
leetNightshade

3
Một phiên bản cải tiến khác của Evan's FileLock có thể được tìm thấy ở đây: github.com/ilastik/lazyflow/blob/master/lazyflow/utility/ù
Stuart Berg

3
OpenStack đã xuất bản bản triển khai của chính họ (tốt, Skip Montanaro) - pylockfile - Rất giống với những gì được đề cập trong các bình luận trước đó, nhưng vẫn đáng xem.
jweyrich

7
@jweyrich Openstacks pylockfile hiện không được chấp nhận. Thay vào đó, nên sử dụng ốc vít hoặc oslo.concurrency .
harbun

2
Một cách thực hiện tương tự khác tôi đoán: github.com/benediktschmitt/py-filelock
herry

39

Có một mô-đun khóa tập tin đa nền tảng ở đây: Portalocker

Mặc dù như Kevin nói, viết vào một tệp từ nhiều quy trình cùng một lúc là điều bạn muốn tránh nếu có thể.

Nếu bạn có thể đưa vấn đề của mình vào cơ sở dữ liệu, bạn có thể sử dụng SQLite. Nó hỗ trợ truy cập đồng thời và xử lý khóa riêng của mình.


16
+1 - SQLite hầu như luôn luôn là cách để đi trong các loại tình huống này.
cdleary

2
Portalocker yêu cầu phần mở rộng Python cho Windows, trên đó.
n611x007

2
@naxa có một biến thể của nó mà chỉ dựa vào MSVCRT và ctypes, xem roundup.hg.sourceforge.net/hgweb/roundup/roundup/file/tip/...
Shmil The Cat

@ n611x007 Portalocker vừa được cập nhật nên không yêu cầu bất kỳ tiện ích mở rộng nào trên Windows nữa :)
Wolph

2
SQLite hỗ trợ truy cập đồng thời?
piotr

23

Các giải pháp khác trích dẫn rất nhiều cơ sở mã bên ngoài. Nếu bạn muốn tự làm điều đó, đây là một số mã cho giải pháp đa nền tảng sử dụng các công cụ khóa tệp tương ứng trên các hệ thống 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        

Bây giờ, AtomicOpencó thể được sử dụng trong một withkhối nơi người ta thường sử dụng một opencâu lệnh.

CẢNH BÁO: Nếu chạy trên Windows và Python gặp sự cố trước khi thoát được gọi, tôi không chắc hành vi khóa sẽ là gì.

CẢNH BÁO: Khóa được cung cấp ở đây là tư vấn, không tuyệt đối. Tất cả các quy trình có khả năng cạnh tranh phải sử dụng lớp "AtomicOpen".


unlock_fileTập tin trên linux không nên gọi fcntllại với LOCK_UNcờ?
eadmaster

Việc mở khóa tự động xảy ra khi đối tượng tập tin được đóng lại. Tuy nhiên, đó là thực tế lập trình xấu của tôi không bao gồm nó. Tôi đã cập nhật mã và thêm hoạt động mở khóa fcntl!
Thomas Lux

Trong __exit__bạn closengoài khóa sau unlock_file. Tôi tin rằng thời gian chạy có thể tuôn ra (tức là ghi) dữ liệu trong thời gian close. Tôi tin rằng người ta phải flushfsyncdưới khóa để đảm bảo không có dữ liệu bổ sung nào được ghi bên ngoài khóa trong suốt thời gian close.
Benjamin Bannier

Cảm ơn vì sự đúng đắn của bạn! Tôi xác nhận rằng có khả năng cho một điều kiện chủng tộc mà không có flushfsync. Tôi đã thêm hai dòng bạn đề nghị trước khi gọi unlock. Tôi đã kiểm tra lại và điều kiện cuộc đua dường như được giải quyết.
Thomas Lux

1
Điều duy nhất sẽ "sai" là do quá trình 1 khóa, tập tin của nó sẽ bị cắt ngắn (nội dung bị xóa). Bạn có thể tự kiểm tra điều này bằng cách thêm một tệp "mở" khác bằng "w" vào mã ở trên trước khi khóa. Điều này là không thể tránh khỏi, bởi vì bạn phải mở tệp trước khi khóa nó. Để làm rõ, "nguyên tử" theo nghĩa là chỉ có nội dung tệp hợp pháp sẽ được tìm thấy trong một tệp. Điều này có nghĩa là bạn sẽ không bao giờ có được một tệp có nội dung từ nhiều quy trình cạnh tranh được trộn lẫn với nhau.
Thomas Lux

15

Tôi thích lockfile - Khóa tệp độc lập với nền tảng


3
Thư viện này có vẻ được viết tốt, nhưng không có cơ chế phát hiện các tệp khóa cũ. Nó theo dõi PID đã tạo ra khóa, vì vậy có thể biết được quá trình đó có còn chạy hay không.
sherbang

1
@sherbang: còn về remove_ex hiện_pidfile thì sao?
Janus Troelsen

@JanusTroelsen mô-đun pidlockfile không thu được khóa nguyên tử.
sherbang

@sherbang Bạn có chắc không? Nó mở tệp khóa với chế độ O_CREAT | O_EXCL.
mhsmith

2
Xin lưu ý rằng thư viện này đã được cung cấp và là một phần của github.com/harlowja/fastulators
congusbongus

13

Tôi đã xem xét một số giải pháp để làm điều đó và lựa chọn của tôi là oslo.concurrency

Đó là tài liệu mạnh mẽ và tương đối tốt. Nó dựa trên ốc vít.

Các giải pháp khác:

  • Portalocker : yêu cầu pywin32, đây là bản cài đặt exe, vì vậy không thể qua pip
  • ốc vít : tài liệu kém
  • lockfile : không dùng nữa
  • flufl.lock : Khóa tệp an toàn NFS cho các hệ thống POSIX.
  • Simpleflock : Bản cập nhật mới nhất 2013-07
  • zc.lockfile : Bản cập nhật mới nhất 2016-06 (kể từ 2017-03)
  • lock_file : Bản cập nhật cuối cùng trong năm 2007-10

re: Portalocker, bây giờ bạn có thể cài đặt pywin32 qua pip thông qua gói pypiwin32.
Timothy Jannace


13

Khóa là nền tảng và thiết bị cụ thể, nhưng nói chung, bạn có một vài tùy chọn:

  1. Sử dụng flock () hoặc tương đương (nếu hệ điều hành của bạn hỗ trợ nó). Đây là khóa tư vấn, trừ khi bạn kiểm tra khóa, nó bị bỏ qua.
  2. Sử dụng phương pháp khóa-sao chép-di chuyển-mở khóa, trong đó bạn sao chép tệp, ghi dữ liệu mới, sau đó di chuyển nó (di chuyển, không sao chép - di chuyển là một hoạt động nguyên tử trong Linux - kiểm tra hệ điều hành của bạn) và bạn kiểm tra sự tồn tại của tập tin khóa.
  3. Sử dụng một thư mục như là một "khóa". Điều này là cần thiết nếu bạn viết thư cho NFS, vì NFS không hỗ trợ flock ().
  4. Cũng có khả năng sử dụng bộ nhớ dùng chung giữa các quy trình, nhưng tôi chưa bao giờ thử điều đó; nó rất đặc trưng cho hệ điều hành.

Đối với tất cả các phương pháp này, bạn sẽ phải sử dụng kỹ thuật khóa xoay (thử lại sau thất bại) để có được và kiểm tra khóa. Điều này không để lại một cửa sổ nhỏ để đồng bộ hóa sai, nhưng nó thường đủ nhỏ để không phải là một vấn đề lớn.

Nếu bạn đang tìm kiếm một giải pháp đa nền tảng, thì tốt hơn hết bạn nên đăng nhập vào hệ thống khác thông qua một số cơ chế khác (điều tốt nhất tiếp theo là kỹ thuật NFS ở trên).

Lưu ý rằng sqlite phải chịu các ràng buộc tương tự đối với NFS như các tệp thông thường, vì vậy bạn không thể ghi vào cơ sở dữ liệu sqlite trên mạng chia sẻ và được đồng bộ hóa miễn phí.


4
Lưu ý: Di chuyển / Đổi tên không phải là nguyên tử trong Win32. Tham khảo: stackoverflow.com/questions/167414/ từ
sherbang

4
Ghi chú mới: os.renamehiện là nguyên tử trong Win32 kể từ Python 3.3: bug.python.org/su8828
Ghostkeeper

7

Phối hợp truy cập vào một tệp duy nhất ở cấp HĐH có nhiều vấn đề mà bạn có thể không muốn giải quyết.

Đặt cược tốt nhất của bạn là có một quy trình riêng biệt phối hợp truy cập đọc / ghi vào tệp đó.


19
"quy trình riêng biệt phối hợp truy cập đọc / ghi vào tệp đó" - nói cách khác, triển khai máy chủ cơ sở dữ liệu :-)
Eli Bendersky

1
Đây thực sự là câu trả lời tốt nhất. Chỉ cần nói "sử dụng máy chủ cơ sở dữ liệu" là quá đơn giản, vì db không phải lúc nào cũng là công cụ phù hợp cho công việc. Điều gì nếu nó cần phải là một tập tin văn bản đơn giản? Một giải pháp tốt có thể là sinh ra một tiến trình con và sau đó truy cập nó thông qua một đường ống có tên, ổ cắm unix hoặc bộ nhớ dùng chung.
Brendon Crawford

9
-1 vì đây chỉ là FUD mà không cần giải thích. Khóa một tập tin để viết có vẻ như là một khái niệm khá đơn giản với tôi rằng các hệ điều hành cung cấp các chức năng như flockcho nó. Một cách tiếp cận "cuộn các đột biến của riêng bạn và một quy trình daemon để quản lý chúng" có vẻ như là một cách tiếp cận khá cực đoan và phức tạp để giải quyết ... một vấn đề bạn chưa thực sự nói với chúng tôi, nhưng chỉ đề xuất một cách thận trọng.
Đánh dấu Amery

-1 vì những lý do được đưa ra bởi @Mark Amery, cũng như đưa ra ý kiến ​​không có căn cứ về vấn đề mà OP muốn giải quyết
Michael Krebs

5

Khóa một tệp thường là một hoạt động dành riêng cho nền tảng, vì vậy bạn có thể cần phải cho phép khả năng chạy trên các hệ điều hành khác nhau. Ví dụ:

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
Bạn có thể đã biết điều này, nhưng mô-đun nền tảng cũng có sẵn để có được thông tin về nền tảng đang chạy. nền tảng.system (). docs.python.org/l Library / pl platform.html .
monkut

2

Tôi đã làm việc trong một tình huống như thế này khi tôi chạy nhiều bản sao của cùng một chương trình từ trong cùng một thư mục / thư mục và lỗi đăng nhập. Cách tiếp cận của tôi là viết một "tệp khóa" vào đĩa trước khi mở tệp nhật ký. Chương trình kiểm tra sự hiện diện của "tệp khóa" trước khi tiếp tục và chờ đến lượt nếu "tệp khóa" tồn tại.

Đây là mã:

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

EDIT --- Sau khi suy nghĩ về một số ý kiến ​​về các khóa cũ ở trên, tôi đã chỉnh sửa mã để thêm kiểm tra tính ổn định của "tệp khóa". Thời gian vài nghìn lần lặp lại của chức năng này trên hệ thống của tôi đã cho và trung bình là 0,002066 ... giây so với trước đó:

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

chỉ sau:

remove('errloglock')

Vì vậy, tôi nghĩ rằng tôi sẽ bắt đầu với số tiền gấp 5 lần để biểu thị sự ổn định và theo dõi tình hình cho các vấn đề.

Ngoài ra, khi tôi đang làm việc với thời gian, tôi nhận ra rằng tôi có một chút mã không thực sự cần thiết:

lock.close()

mà tôi đã ngay lập tức làm theo tuyên bố mở, vì vậy tôi đã loại bỏ nó trong chỉnh sửa này.


2

Để thêm vào câu trả lời của Evan Fossmark , đây là một ví dụ về cách sử dụng 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()

Bất kỳ mã nào trong with lock:khối đều an toàn cho luồng, nghĩa là nó sẽ được hoàn thành trước khi quá trình khác có quyền truy cập vào tệp.


1

Các kịch bản là như thế: Người dùng yêu cầu một tập tin để làm điều gì đó. Sau đó, nếu người dùng gửi lại cùng một yêu cầu, nó sẽ thông báo cho người dùng rằng yêu cầu thứ hai không được thực hiện cho đến khi yêu cầu đầu tiên kết thúc. Đó là lý do tại sao, tôi sử dụng cơ chế khóa để xử lý vấn đề này.

Đây là mã làm việc của tôi:

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

Tôi tìm thấy một cách thực hiện đơn giản và hiệu quả (!) Từ Grizzled-python.

Sử dụng đơn giản os.open (..., O_EXCL) + os.close () không hoạt động trên windows.


4
Tùy chọn O_EXCL không liên quan đến khóa
Sergei

0

Bạn có thể thấy pylocker rất hữu ích. Nó có thể được sử dụng để khóa một tệp hoặc cho các cơ chế khóa nói chung và có thể được truy cập từ nhiều quy trình Python cùng một lúc.

Nếu bạn chỉ muốn khóa một tập tin ở đây thì nó hoạt động như thế nào:

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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.