Ghi tệp đồng thời Python

“Bạn không bắt buộc phải thắng. Bạn có nghĩa vụ phải tiếp tục cố gắng. Để tốt nhất bạn có thể làm hàng ngày. " - Jason Mraz

nội dung

1. Giới thiệu

Quản lý tài nguyên trong các chương trình đa luồng là một tình huống khó khăn. Ví dụ: giả sử bạn có một biến đếm được đọc và sửa đổi bởi nhiều luồng. Nếu bạn không thực hiện cẩn thận, có thể các giá trị không nhất quán được đọc và/hoặc lan truyền. Điều này dẫn đến các lỗi tinh vi và khó gỡ lỗi

Một giải pháp để quản lý quyền truy cập vào tài nguyên được chia sẻ giữa các luồng là sử dụng khóa. Một luồng phải có khóa trước khi truy cập tài nguyên được chia sẻ. Nếu một luồng khác đang sử dụng tài nguyên, luồng đầu tiên sẽ đợi cho đến khi khóa được giải phóng. Khi nhiều luồng đang chờ khóa, một trong các luồng sẽ được đánh thức và có thể lấy được khóa

Hãy để chúng tôi kiểm tra cách thức hoạt động của nó đối với việc đọc và ghi một tệp từ nhiều luồng. Chúng tôi có một ứng dụng có nhiều luồng worker, mỗi luồng cần đọc và cập nhật một tệp

2. Đọc và ghi tệp mà không cần khóa

Để đơn giản, chúng ta hãy giả sử rằng hàm worker cần đọc dòng cuối cùng của tệp, tăng số tìm thấy ở đó và ghi lại. Như được giải thích bên dưới, chúng tôi có sự sắp xếp này để có thể theo dõi việc chà đạp nhiều luồng

Đây là chức năng và nó không sử dụng khóa để truy cập tài nguyên được chia sẻ

def runner[fname]:
    with open[fname, 'r'] as f:
        for ln in f:
            n = int[ln]
    n += 1
    with open[fname, 'a'] as f:
        f.write[str[n] + '\n']

3. Chạy chức năng công nhân trong nhiều chủ đề

Còn đây là phần chính của chương trình tạo và khởi động nhiều luồng để chạy hàm trên. Như đã giải thích ở trên, mỗi luồng cố gắng đọc và tăng giá trị trên dòng cuối cùng. Điều này được thiết lập để chúng tôi có thể theo dõi nhiều luồng can thiệp lẫn nhau

Ví dụ: một luồng có thể đọc giá trị 20. Trước khi nó tăng giá trị lên 21 và ghi lại, một luồng khác có thể đến và đọc cùng một giá trị, 20. Và luồng thứ hai cũng ghi giá trị 21. Vì vậy, nếu nhiều giá trị được tìm thấy trong tệp đầu ra, điều đó có nghĩa là nhiều luồng đã can thiệp lẫn nhau trong việc cập nhật tài nguyên được chia sẻ

# filename to use
fname = sys.argv[1] if len[sys.argv] > 1 else 'counter.txt'

# re-initialize file
with open[fname, 'w'] as f:
    f.write['0\n']

threads = []
for i in xrange[0, 50]:
    t = threading.Thread[target = runner, args = [fname, ]]
    threads.append[t]
    t.start[]

for t in threads:
    t.join[]

Khi chạy chương trình này, người ta thấy rằng các giá trị được viết đúng khoảng 9 trên 10 lần. Và, loại lỗi ngẫu nhiên này là nguyên nhân khiến những lỗi này khó theo dõi

4. Phần mã khóa để cập nhật tệp

Bây giờ chúng ta hãy cập nhật chức năng task worker để lấy khóa trước khi thực hiện bất kỳ thao tác nào khác [Điều này hơi giống với việc sử dụng từ khóa được đồng bộ hóa trong java. ]

Đây là chức năng được cập nhật. Nó sử dụng một biến toàn cục lck và sử dụng các phương thức thu thập [] và giải phóng [] cho các phần quan trọng

def runner[fname]:
    global lck
    lck.acquire[]
    with open[fname, 'r'] as f:
        for ln in f:
            n = int[ln]
    n += 1
    with open[fname, 'a'] as f:
        f.write[str[n] + '\n']
    lck.release[]

5. Tạo khóa

Bản thân khóa được tạo trong chương trình chính. Ngoại trừ điều đó, không có thay đổi nào khác. Khóa được tạo bằng luồng. Khóa[]

lck = threading.Lock[]

# filename to use
fname = sys.argv[1] if len[sys.argv] > 1 else 'counter.txt'

# re-initialize file
with open[fname, 'w'] as f:
    f.write['0\n']

threads = []
for i in xrange[0, 50]:
    t = threading.Thread[target = runner, args = [fname, ]]
    threads.append[t]
    t.start[]

for t in threads:
    t.join[]

Khi chúng tôi chạy chương trình đã cập nhật này, sẽ không còn tranh chấp tài nguyên nữa và các bản cập nhật cho tệp được bảo vệ khỏi bị giả mạo trên nhiều luồng

Phần kết luận

Bài viết này chỉ ra một cách tiếp cận để giải quyết tranh chấp luồng đối với các tài nguyên quan trọng. Không có khóa, các luồng sẽ giẫm lên nhau và kết quả không thể đoán trước. Sử dụng khóa tuần tự hóa quyền truy cập vào tài nguyên được chia sẻ và tranh chấp luồng bị loại bỏ

Trong bài viết này, các nguyên tắc cơ bản của đa luồng được giới thiệu. Chúng tôi đã giới thiệu các khái niệm về quy trình, luồng, Khóa thông dịch viên toàn cầu [GIL], tác vụ liên kết với CPU và tác vụ liên kết với IO. Bây giờ chúng tôi có thể tạo các ứng dụng theo luồng với threading.Thread gốc và concurrent.futures.ThreadPoolExecutor cấp cao hơn. Chúng tôi cũng đã giới thiệu cách xử lý đúng cách các ngoại lệ được nêu trong luồng để chương trình không bị lỗi âm thầm. Cuối cùng, chúng tôi đã xây dựng một hệ thống nhắn tin không đồng bộ Nhà xuất bản/Người đăng ký [Pub/Sub] đơn giản với hàng đợi và đa luồng, kết hợp tất cả các tính năng nâng cao của đa luồng mà chúng tôi đã giới thiệu trong hướng dẫn này

Nhiều luồng có thể ghi vào cùng một tệp Python không?

Việc ghi vào tệp không an toàn cho chủ đề . Việc ghi đồng thời vào cùng một tệp từ nhiều luồng không phải là luồng an toàn và có thể dẫn đến tình trạng tương tranh .

Hai luồng có thể ghi vào cùng một tệp không?

Mặc dù, có, bạn có thể chia sẻ một phần xử lý tệp giữa các luồng , nhưng việc ghi vào tệp là một thao tác chậm và việc bảo vệ tệp đó có nghĩa là chặn .

Python có thể chạy đồng thời không?

Trên thực tế, một quy trình Python không thể chạy các luồng song song nhưng nó có thể chạy chúng đồng thời thông qua chuyển đổi ngữ cảnh trong các hoạt động liên kết I/O . Hạn chế này thực sự được thi hành bởi GIL. Khóa phiên dịch toàn cầu Python [GIL] ngăn các luồng trong cùng một quy trình được thực thi cùng một lúc.

Tại sao đa luồng không thể thực hiện được trong Python?

Python không hỗ trợ đa luồng vì Python trên trình thông dịch Cpython không hỗ trợ thực thi đa lõi thực sự thông qua đa luồng . Tuy nhiên, Python không có thư viện luồng. GIL không ngăn luồng.

Chủ Đề