File locking on simultaneous access [Part 2]

Trong Part 1 tôi đã nói đến việc xử lý file-locking từ userspace. Giờ chúng ta sẽ thử một cách xử lý khác từ kernel-space.

Linux hỗ trợ 2 kiểu file locking là "advisory locking" và "mandatory locking".

"Advisory locking" nghĩa là các process thao tác trên cùng một file phải dựa vào cùng một cơ chế để biết đc rằng file đó có đang được xử lý bởi process khác không, rồi tự quyết định có thao tác trên file đó hay ko. Kernel ở đây chỉ có nhiệm vụ "tư vấn" cho process đó biết chứ nó ko ép buộc đc process là ko được thao tác trên file đó chẳng hạn.

Còn với "mandatory locking", khi một process đã thông báo với kernel rằng nó đang thao tác trên file, thì các process khác sẽ ko thể thao tác trên file đó, cho dù có cố tình.

Ở đây chúng ta chỉ tạm bàn tới "advisory locking".

Linux cung cấp 2 system call để phục vụ cho "advisory locking" là fcntl() và flock().
Với fcntl() chúng ta có thể lock bất kỳ một phân đoạn nào của file, thậm chí là 1 byte, còn với flock() thì chúng ta chỉ có thể lock nguyên cả file.
Lưu ý là các process khi thao tác trên cùng 1 file phải sử dụng cùng 1 kiểu lock, các process dùng fcntl() sẽ ko thể cooperate với các process dùng flock() và ngược lại.

    #!/usr/bin/env python 
    """ Filename: update_ifaces.py """
    import fcntl, sys
    from time import sleep
    from random import random

    def update_ifaces_dict(ifaces_file, data):
        sleep(1)

    def lockFile(lockfile, data):
        fp = open(lockfile, 'r+b')
        try:
            fcntl.lockf(fp, fcntl.LOCK_EX | fcntl.LOCK_NB)
            print "Do job..", sys.argv[2]
            update_ifaces_dict(lockfile, data)
            print "DONE job", sys.argv[2]
        except IOError:
            return False

        return True

    if __name__ == "__main__":
        try:
            data = { 'iface': sys.argv[1],
                     'addr': sys.argv[4],
                     'gw': sys.argv[5],
                     'name': sys.argv[6],
                   }
        except:
            data = {}

        while not lockFile("/tmp/ifaces.obj", data):
            pass

Chạy test bằng lệnh

    for i in `seq 1 10`; do python update_ifaces.py iface $i pad_param 4.4.4.4 4.4.4.1 wan1 & done

Oh wait, watch your CPU! It's burning ;)
Replace the "pass" command by something like:

            sleep(random())  # waiting for my turn

PS: Cách làm này nhanh hơn đáng kể khi số lượng process lớn, có thể nhìn thấy rõ ràng với số process khoảng 100.
Nguyên do không phải là do OS native nhanh hơn, mà là do ko gặp phải trường hợp nhiều process đồng thời check thấy lock-file chưa tồn tại nên đông thời tạo lock-file(như ở Part 1) --> phải vào 1 vòng lặp đợi mới. Tuy nhiên cách làm này vẫn chưa tối ưu do vẫn phải đợi trong một vòng lặp ngoài để đến lượt mình.

Câu hỏi đặt ra: liệu có phương thức nào nhanh hơn không?

(...to be continued)