Flock 锁的详细使用指南
12月 24, 2025
一、Flock锁的使用方法#
1. 基本操作方式#
flock支持两种主要操作模式:
- 阻塞模式:当无法获取锁时,进程会一直等待直到锁可用
- 非阻塞模式:当无法获取锁时立即返回,通常会返回一个错误
2. 锁类型#
- 共享锁(SHARED/LOCK_SH):允许多个进程同时持有,适用于读操作
- 排他锁(EXCLUSIVE/LOCK_EX):只允许一个进程持有,适用于写操作
- 解锁(UNLOCK/LOCK_UN):释放已获取的锁
3. 锁的特性#
- 基于文件描述符:锁与文件描述符关联,而非文件路径
- 自动释放:进程终止或文件关闭时锁自动释放
- 不支持嵌套:同一进程对同一文件多次加锁不会累积
- 建议性锁:flock是建议性锁,不强制阻止访问,需要所有参与者遵守
二、典型使用场景#
- 单实例应用控制:确保同一时间只有一个程序实例运行
- 文件写保护:防止多个进程同时写入同一文件导致数据损坏
- 批处理作业协调:确保资源密集型任务不同时执行
- 定时任务防重叠:防止长时间运行的cron任务重叠执行
- 临界区保护:保护共享资源的访问,如缓存更新
三、Shell使用案例#
1. 基本语法#
flock [options] <file|directory|fd> <command>2. 单实例脚本保护#
#!/bin/bash
LOCK_FILE="/tmp/my_script.lock"
# 尝试获取排他锁,如果失败则退出
exec 200>"$LOCK_FILE"
flock -n 200 || {
echo "Another instance is running"
exit 1
}
# 脚本主体
echo "Running critical section at $(date)"
sleep 10
# 自动释放锁(脚本结束时文件描述符关闭)3. 保护文件写入操作#
#!/bin/bash
LOG_FILE="/var/log/app.log"
LOCK_FILE="/var/lock/app.log.lock"
# 使用flock命令直接保护写入操作
flock -x "$LOCK_FILE" -- \
echo "$(date): New log entry" >> "$LOG_FILE"4. 非阻塞模式与超时#
#!/bin/bash
LOCK_FILE="/tmp/process.lock"
# 非阻塞模式,如果无法获取锁立即返回
if ! flock -n 9; then
echo "Cannot acquire lock, exiting"
exit 1
fi 9>"$LOCK_FILE"
# 临界区代码
echo "Processing..."四、Python使用案例#
1. 基本文件锁实现#
import fcntl
import time
def write_with_lock(filename, content):
with open(filename, 'a') as f:
# 获取排他锁
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
# 写入操作
f.write(content + '\n')
f.flush()
finally:
# 释放锁
fcntl.flock(f.fileno(), fcntl.LOCK_UN)2. 上下文管理器实现(推荐)#
import fcntl
import os
class FileLock:
def __init__(self, lock_file, timeout=None):
self.lock_file = lock_file
self.timeout = timeout
self.fd = None
def __enter__(self):
self.fd = open(self.lock_file, 'w+')
start_time = time.time()
while True:
try:
fcntl.flock(self.fd.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
break
except IOError as e:
if self.timeout is not None and time.time() - start_time > self.timeout:
raise TimeoutError("Could not acquire lock within timeout")
time.sleep(0.1)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
fcntl.flock(self.fd.fileno(), fcntl.LOCK_UN)
self.fd.close()
# 使用示例
try:
with FileLock("/tmp/myapp.lock", timeout=5):
print("Lock acquired, performing critical operation")
# 执行临界区代码
except TimeoutError:
print("Could not acquire lock within timeout period")3. 保护配置文件更新#
import json
import fcntl
import os
def update_config(config_path, update_func):
"""安全更新JSON配置文件"""
# 读取现有配置
with open(config_path, 'r+') as f:
# 获取排他锁
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
try:
# 读取内容
config = json.load(f)
# 应用更新
updated_config = update_func(config)
# 回到文件开始位置
f.seek(0)
f.truncate()
# 写入更新后的内容
json.dump(updated_config, f, indent=2)
f.flush()
os.fsync(f.fileno()) # 确保写入磁盘
finally:
# 释放锁
fcntl.flock(f.fileno(), fcntl.LOCK_UN)五、注意事项与最佳实践#
锁文件位置:
- 通常放在
/tmp或/var/lock目录 - 确保所有进程对锁文件有相同的访问路径
- 通常放在
超时处理:
- 避免死锁,实现适当的超时机制
- 记录锁等待情况,便于问题排查
错误处理:
- 捕获可能的IO异常
- 确保锁在异常情况下也能正确释放
性能考量:
- 避免长时间持有锁
- 仅在必要时使用排他锁,读操作可使用共享锁
清理机制:
- 考虑实现锁文件的定期清理
- 处理僵尸进程可能遗留的锁
flock是一种简单但强大的进程同步机制,适用于各种需要文件级锁定的场景。在实现时,应根据具体需求选择适当的锁类型和超时策略,以平衡安全性和性能。
200 文件描述符编号#
在shell脚本中,200是一个文件描述符(File Descriptor)编号。让我详细解释:
文件描述符基础#
在Unix/Linux系统中,文件描述符是一个非负整数,用于引用进程打开的文件。系统默认分配:
0= 标准输入 (stdin)1= 标准输出 (stdout)2= 标准错误 (stderr)
当程序需要打开额外文件时,系统会分配下一个可用的数字(通常是3、4、5等)。
为什么使用200?#
在flock的shell使用中,使用像200这样的大数字作为文件描述符是最佳实践,原因如下:
避免冲突:shell脚本内部可能使用3-9等低编号文件描述符用于其他目的,使用高位数字(如200)几乎不会与系统或其他命令的默认文件描述符冲突
明确意图:大数字清楚表明这是一个专门用于锁定的文件描述符,而非普通I/O
可读性:在脚本中看到200这样的数字,经验丰富的开发者会立即识别这是用于flock的专用文件描述符
完整工作流程#
# 1. 将文件描述符200关联到锁文件(打开文件)
exec 200>"$LOCK_FILE"
# 2. 对文件描述符200应用非阻塞排他锁
flock -n 200 || {
echo "Another instance is running"
exit 1
}
# 3. 脚本执行主体
echo "Critical section running"
# 4. 脚本结束时自动关闭文件描述符200,释放锁
# (也可以显式关闭:exec 200>&-)实际等效操作#
这个操作等效于以下C语言伪代码:
int fd = open("/tmp/my_script.lock", O_RDWR|O_CREAT, 0644); // fd=200
if (flock(fd, LOCK_EX|LOCK_NB) != 0) { // 非阻塞尝试获取排他锁
printf("Another instance is running\n");
exit(1);
}
// ... 执行临界区代码 ...
close(fd); // 自动释放锁
其他常见用法#
也可以使用较小的数字(但不推荐):
exec 3>"$LOCK_FILE" # 使用文件描述符3
flock -n 3 || exit 1或者直接在命令中使用:
flock -n /tmp/lockfile.lock -c "command_to_run"关键点:200只是一个任意选择的大数字文件描述符,用于安全地关联锁文件,避免与shell可能使用的其他低编号文件描述符发生冲突。这个数字本身没有特殊含义,你可以使用199、201或999等任何未被使用的高位数字。