lockfile 是一个非常实用的库,用于在文件系统中创建和管理锁文件,这在多进程或多线程环境中非常有用,可以防止多个进程同时写入同一个文件而导致的数据损坏或竞争条件。

lockfile 的两种主要类型
在安装和使用之前,需要了解 lockfile 有两个主要的分支:
python-lockfile(推荐): 这是一个更现代、维护得更好的分支,支持 Python 2 和 Python 3,它提供了LockFile、LockedFile等核心类,功能强大且稳定。我们通常指的就是这个库。lockfile(旧版): 这是原始的库,主要针对 Python 2,现在已基本不再维护。
在绝大多数情况下,你应该安装和使用 python-lockfile。
安装 python-lockfile
安装非常简单,使用 pip 即可,打开你的终端或命令行工具,运行以下命令:
pip install lockfile
注意事项:

-
权限问题: 如果你在 Linux 或 macOS 上遇到权限错误(
Permission denied),可以尝试使用sudo:sudo pip install lockfile
或者,更好的做法是使用
--user标志将包安装到你的用户目录下,避免系统权限问题:pip install --user lockfile
-
Python 环境: 如果你使用的是虚拟环境(如
venv或conda),请确保已经激活了对应的虚拟环境,然后再运行安装命令,这样可以确保包被安装到正确的环境中。 -
验证安装: 安装完成后,你可以在 Python 交互式环境中导入它来验证是否成功:
(图片来源网络,侵删)>>> import lockfile >>> print(lockfile.__version__) # 如果没有版本号信息,或者没有报错,说明安装成功
如何使用 lockfile
lockfile 的核心思想是:通过创建一个特殊的锁文件来标记资源(如数据文件)正在被使用,其他进程在尝试访问该资源前,会先检查锁文件是否存在,如果存在,它们会等待或直接失败,直到锁文件被释放。
基本用法:LockFile
这是最简单的锁,它是一个非阻塞锁,如果锁已经被其他进程获取,再次尝试获取时会立即抛出 AlreadyLocked 异常。
import lockfile
import time
import os
# 假设我们要锁定的文件是 'my_data.txt'
DATA_FILE = 'my_data.txt'
LOCK_FILE = DATA_FILE + '.lock' # lockfile 会自动生成这个锁文件名
def write_to_file_with_lock(content):
try:
# 1. 尝试获取锁
# LockFile 对象一被创建,就会立即尝试获取锁
lock = lockfile.LockFile(LOCK_FILE)
print(f"进程 {os.getpid()} 成功获取锁。")
# 2. 执行需要互斥的操作(例如写入文件)
print(f"进程 {os.getpid()} 正在写入数据...")
with open(DATA_FILE, 'a') as f:
f.write(content + '\n')
# 模拟一些耗时操作
time.sleep(5)
except lockfile.AlreadyLocked:
print(f"进程 {os.getpid()} 获取锁失败,文件已被锁定。")
except lockfile.LockTimeout:
print(f"进程 {os.getpid()} 获取锁超时。")
finally:
# 3. 释放锁
# 在 finally 块中确保锁一定会被释放
if 'lock' in locals():
lock.release()
print(f"进程 {os.getpid()} 已释放锁。")
# --- 模拟多进程 ---
# 在一个终端运行这个脚本,它会等待 5 秒
# write_to_file_with_lock("第一条数据,来自进程 A")
# 在另一个终端快速运行这个脚本,它会立刻提示获取锁失败
# write_to_file_with_lock("第二条数据,来自进程 B")
进阶用法:PIDLockFile
这是一个更智能的锁,它会将获取锁的进程 ID (PID) 写入锁文件,如果一个进程崩溃了,锁文件会残留,但 PIDLockFile 在获取锁时会检查锁文件中的 PID 是否仍然存在,如果不存在,它会认为旧的锁已经失效,并安全地获取新锁。
import lockfile
import os
import time
import sys
DATA_FILE = 'my_data_pid.txt'
LOCK_FILE = DATA_FILE + '.pidlock'
def write_with_pid_lock(content):
try:
# 使用 PIDLockFile
# 它会检查锁文件中的进程是否还在运行
lock = lockfile.PIDLockFile(LOCK_FILE)
print(f"进程 {os.getpid()} 尝试获取 PID 锁...")
# 非阻塞式获取,如果失败则抛出 AlreadyLocked
lock.acquire()
print(f"进程 {os.getpid()} 成功获取 PID 锁。")
# 执行操作
with open(DATA_FILE, 'a') as f:
f.write(f"{content} (by {os.getpid()})\n")
time.sleep(3)
except lockfile.AlreadyLocked:
print(f"进程 {os.getpid()} 获取 PID 锁失败。")
finally:
if 'lock' in locals() and lock.i_am_locking():
lock.release()
print(f"进程 {os.getpid()} 已释放 PID 锁。")
# write_with_pid_lock("测试 PID 锁")
带超时的锁
有时候你不希望程序因为获取不到锁而无限期等待,而是希望等待一段时间后放弃,这时可以使用 LockTimeout 异常。
import lockfile
import time
import os
DATA_FILE = 'my_data_timeout.txt'
LOCK_FILE = DATA_FILE + '.timeoutlock'
def write_with_timeout(content, timeout=2):
try:
lock = lockfile.LockFile(LOCK_FILE)
# 尝试获取锁,最多等待 timeout 秒
lock.acquire(timeout)
print(f"进程 {os.getpid()} 在等待 {timeout} 秒后成功获取锁。")
with open(DATA_FILE, 'a') as f:
f.write(content + '\n')
time.sleep(1) # 操作本身很快
except lockfile.LockTimeout:
print(f"进程 {os.getpid()} 等待 {timeout} 秒后仍未获取到锁,已放弃。")
except lockfile.AlreadyLocked:
print(f"进程 {os.getpid()} 锁已被永久占用。")
finally:
if 'lock' in locals():
lock.release()
print(f"进程 {os.getpid()} 已释放锁。")
# write_with_timeout("使用超时机制的写入", timeout=2)
重要注意事项和最佳实践
finally块是必须的:一定要将lock.release()放在finally块中,这样可以确保即使命令在try块中因异常(如KeyboardInterrupt)而中断,锁也一定会被释放,避免死锁。- 原子性:
lockfile的获取和释放操作是原子性的,这意味着它们不会被其他操作打断,保证了锁的可靠性。 - 锁文件的位置:锁文件通常应该和你要锁定的数据文件放在同一个文件系统上,这能保证文件操作的原子性(
rename操作在同一个文件系统上是原子的)。 - 清理:如果程序异常终止,锁文件可能会遗留下来。
PIDLockFile在一定程度上可以缓解这个问题,因为它会检查进程是否存活,但最可靠的清理方式还是确保你的程序总能正确地释放锁(通过finally块)。 - 替代方案:对于更复杂的并发控制,特别是分布式系统,可以考虑使用其他工具:
fcntl/msvcrt: Python 标准库中提供的文件锁定机制,但它们的行为在不同操作系统上可能不一致,且仅在单个机器上有效。zookeeper/etcd: 分布式协调服务,可以用于构建分布式锁。Redis: 可以利用其SETNX(Set if Not eXists) 命令来实现分布式锁。portalocker: 另一个流行的文件锁库,提供了跨平台的fcntl风格的接口。
| 任务 | 命令 / 代码 |
|---|---|
| 安装 | pip install lockfile |
| 基本导入 | import lockfile |
| 创建非阻塞锁 | lock = lockfile.LockFile('my_app.lock') |
| 创建PID锁 | lock = lockfile.PIDLockFile('my_app.lock') |
| 获取锁(非阻塞) | lock.acquire() (失败则抛出 AlreadyLocked) |
| 获取锁(带超时) | lock.acquire(timeout=10) (失败则抛出 LockTimeout) |
| 释放锁 | lock.release() |
| 检查是否持有锁 | lock.i_am_locking() |
希望这份详细的指南能帮助你成功安装和使用 lockfile!
