Python使用fcntl文件锁

前言

Python语言中的文件锁可以使用 fcntl 库,它实际上是对Unix系统上的 fcntlioctl 函数提供了一个接口。官网描述中是这样形容的:

This module performs file control and I/O control on file descriptors

这个模块提供了针对文件描述符的文件控制和I/O控制。常用的是在多进程/线程读写同一文件时需要使用的文件锁功能。

模块函数

· fcntl.fcntl(fd, cmd, arg=0)
· fcntl.ioctl(fd, request, arg=0, mutate_flag=True)
· fcntl.flock(fd, operation)
· fcntl.lockf(fd, cmd, len=0, start=0, whence=0)

自己暂时只用到了 fcntl.flock 这个函数对文件上锁,在此做一些记录。

使用

考虑两个进程对同一个文件进行读写操作:
目录下有一个test.json文件内容如下:

{
    "test1": {
        "1000": {
            "files": "5"
        },
        "1001": {
            "files": "6"
        }
    },
    "test2": {
        "2000": {
            "files": "0"
        },
        "2001": {
            "files": "1"
        }
    }
}

处理代码如下,模拟两个进程对该文件进行写操作:

import json
import time
import fcntl
import multiprocessing as mp

test_dict = {
    "test1": {
        "1001": {
            "files": "6"
        }
    },
    "test2": {
        "2000": {
            "files": "0"
        },
        "2001": {
            "files": "1"
        }
    }
}

def test1(json_path):
    with open(json_path, 'r+') as test_file:
        fcntl.flock(test_file.fileno(), fcntl.LOCK_EX) #加锁,with块外自动释放锁
        json_content = test_dict
        # time.sleep(5)
        test_file.truncate()#手动清空
        json.dump(json_content, test_file, sort_keys=True, indent=4, separators=(',', ': '))

def test2(json_path):
    with open(json_path, 'r+') as test_file:
        fcntl.flock(test_file.fileno(), fcntl.LOCK_EX)
        test_dict['test1'][str(3001)] = {"files":str(7)}
        json_content = test_dict
        test_file.truncate()
        json.dump(json_content, test_file, sort_keys=True, indent=4, separators=(',', ': '))

if __name__ == '__main__':
    json_path = r'./test.json'
    p = mp.Process(target=test1, args=(json_path,))
    q = mp.Process(target=test2, args=(json_path,))
    p.start()
    q.start()
    p.join()
    q.join()

分析:
进程p先启动后获取到文件锁,以r+模式打开test.json文件,使用with...open...打开时文件内容不会清空,所以在多进程读写同一文件时需要特别注意文件的打开方式。在写入前使用truncate()函数手动清空内容,最终文件内容如下:

{
    "test1": {
        "1001": {
            "files": "6"
        }
    },
    "test2": {
        "2000": {
            "files": "0"
        },
        "2001": {
            "files": "1"
        }
    }
}

进程q后启动,在with...open...后希望获取文件锁,此时如果进程p打开的文件还未处理结束,文件锁还未释放之前,q会一直处于阻塞状态。直到进程pwith...open...代码块结束后文件锁自动释放(也可以选择手动释放文件锁),进程q才获取到文件锁,开始往test.json文件中写入内容。最终文件内容如下:

{
    "test1": {
        "1001": {
            "files": "6"
        },
        "3001": {
            "files": "7"
        },
    },
    "test2": {
        "2000": {
            "files": "0"
        },
        "2001": {
            "files": "1"
        }
    }
}

两个进程读写同一文件,假设进程p先启动先获得文件锁,进程q后启动后获得文件锁:

进程p进程q说明
p读到旧文件内容后释放锁,q获得锁后写新内容到文件
不涉及文件内容修改,均读到同样的文件内容
p写新内容到文件后释放锁,q获得锁后读到新文件内容
p写新内容到文件后释放锁,q获得锁后写新内容到文件

多进程读写同一文件时均需要考虑加锁,否则会出现异常情况。例如在进程p获得文件锁后正在写文件,进程q不考虑加锁直接读取该文件,可能会由于文件内容被进程p清空(包括“以w模式打开时清空”和“以truncate()方式清空”)导致读取出错。当读取操作也加锁时,如果进程p未结束文件锁未被释放,那么进程q将一直等待,直到得到锁后读取文件,这时不会出错。

总结:
(1)在打开一个文件时,以w模式和以r+模式打开时表现不同。其中以w模式打开时会清空文件内容,写入时是将新内容写入文件;以r+模式打开时不会清空文件内容,写入时是从游标0处写入新内容,但旧内容如果长于新内容,则会出现未被覆盖掉的旧内容,导致内容出错。需要特别注意。
(2)文件锁不要长期占用,处理结束后尽快释放掉。
(3)多进程读写同一文件时避免以w(以及w+等)模式打开文件,否则打开瞬间文件内容即被清空。
(4)加文件锁写json的代码:

def server_json_dump(json_content, json_path):
    """写json"""
    if os.path.exists(json_path):
        mode = 'r+'#以r+模式打开避免with...open打开文件时自动清空内容同时被读取
    else:
        mode = 'w'#以w模式打开以新建文件
    with open(json_path, mode=mode) as result_file:#以r+形式打开
        fcntl.flock(result_file.fileno(), fcntl.LOCK_EX) #加锁,with块外自动释放锁
        result_file.truncate()#手动清空,避免r+模式下文本重叠
        json.dump(json_content, result_file, sort_keys=True, indent=4, separators=(',', ': '))

在这里插入图片描述

参考资料

[1] fcntl — The fcntl and ioctl system calls
[2] Python 标准库之 fcntl
[3] Python open函数
[4] Python清空文件并替换内容
[5] 文件锁 python 进程间锁 fcntl

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TracelessLe

❀点个赞加个关注再走吧❀

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值