Flock 锁的详细使用指南

12月 24, 2025
并发编程, Linux, Shell, Python, ByAI, 操作系统

一、Flock锁的使用方法#

1. 基本操作方式#

flock支持两种主要操作模式:

  • 阻塞模式:当无法获取锁时,进程会一直等待直到锁可用
  • 非阻塞模式:当无法获取锁时立即返回,通常会返回一个错误

2. 锁类型#

  • 共享锁(SHARED/LOCK_SH):允许多个进程同时持有,适用于读操作
  • 排他锁(EXCLUSIVE/LOCK_EX):只允许一个进程持有,适用于写操作
  • 解锁(UNLOCK/LOCK_UN):释放已获取的锁

3. 锁的特性#

  • 基于文件描述符:锁与文件描述符关联,而非文件路径
  • 自动释放:进程终止或文件关闭时锁自动释放
  • 不支持嵌套:同一进程对同一文件多次加锁不会累积
  • 建议性锁:flock是建议性锁,不强制阻止访问,需要所有参与者遵守

二、典型使用场景#

  1. 单实例应用控制:确保同一时间只有一个程序实例运行
  2. 文件写保护:防止多个进程同时写入同一文件导致数据损坏
  3. 批处理作业协调:确保资源密集型任务不同时执行
  4. 定时任务防重叠:防止长时间运行的cron任务重叠执行
  5. 临界区保护:保护共享资源的访问,如缓存更新

三、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)

五、注意事项与最佳实践#

  1. 锁文件位置

    • 通常放在/tmp/var/lock目录
    • 确保所有进程对锁文件有相同的访问路径
  2. 超时处理

    • 避免死锁,实现适当的超时机制
    • 记录锁等待情况,便于问题排查
  3. 错误处理

    • 捕获可能的IO异常
    • 确保锁在异常情况下也能正确释放
  4. 性能考量

    • 避免长时间持有锁
    • 仅在必要时使用排他锁,读操作可使用共享锁
  5. 清理机制

    • 考虑实现锁文件的定期清理
    • 处理僵尸进程可能遗留的锁

flock是一种简单但强大的进程同步机制,适用于各种需要文件级锁定的场景。在实现时,应根据具体需求选择适当的锁类型和超时策略,以平衡安全性和性能。

200 文件描述符编号#

在shell脚本中,200是一个文件描述符(File Descriptor)编号。让我详细解释:

文件描述符基础#

在Unix/Linux系统中,文件描述符是一个非负整数,用于引用进程打开的文件。系统默认分配:

  • 0 = 标准输入 (stdin)
  • 1 = 标准输出 (stdout)
  • 2 = 标准错误 (stderr)

当程序需要打开额外文件时,系统会分配下一个可用的数字(通常是3、4、5等)。

为什么使用200?#

在flock的shell使用中,使用像200这样的大数字作为文件描述符是最佳实践,原因如下:

  1. 避免冲突:shell脚本内部可能使用3-9等低编号文件描述符用于其他目的,使用高位数字(如200)几乎不会与系统或其他命令的默认文件描述符冲突

  2. 明确意图:大数字清楚表明这是一个专门用于锁定的文件描述符,而非普通I/O

  3. 可读性:在脚本中看到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等任何未被使用的高位数字。

本文共 1985 字,上次修改于 Dec 25, 2025,以 CC 署名-非商业性使用-禁止演绎 4.0 国际 协议进行许可。

相关文章

» 使用 GDB 调试 Python

» Shell Script to Check if a File Exists

» tar 命令中的绝对路径和相对路径使用注意

» CIDR 表示法

» mount 挂载命令