Linux文件锁解决多进程并发

1、多进程开发的三大问题

首先小哥介绍一下遇到的三个问题:

1. 日志系统的行交错问题

当多个进程同时向同一日志文件写入数据时,内核的I/O缓冲区可能导致日志行交错。例如:

复制代码
进程A写入:2025-05-01 10:00:01 [A] Start task...
进程B写入:2025-05-01 10:00:02 [B] Error: file not found
进程A写入:2025-05-01 10:00:03 [A] Task completed.

实际日志可能变为:

复制代码
2025-05-01 10:00:01 [A] Start task...2023-10-01 10:00:02 [B] Error: file not found
2025-05-01 10:00:03 [A] Task completed.

格式错乱这样会使得日志可读性极差,无法用于问题排查。

2. 配置文件更新导致损坏

若两个进程同时修改配置文件(如 config.json):

• 进程A写入前半部分内容后,进程B抢占了写入权。

• 进程B覆盖了进程A的部分内容,导致文件内容不完整。

配置文件数据错乱,严重点会导致服务崩溃。

3. 单例进程重复启动

守护进程(如 daemon)通常要求仅允许一个实例运行。若未加锁,当用户误操作重复启动进程,导致资源竞争(如端口占用)。最终导致服务不可用。

2、文件锁来了

文件锁主要是通过内核机制协调多进程对文件的并发访问,解决以下问题:

• 互斥访问:同一时间仅允许一个进程修改文件。

• 数据一致性:确保文件内容的完整性和操作原子性。

• 进程同步:避免资源竞争导致的不可预测行为。

然而linux系统中通常有两类文件锁:

  1. 建议性锁(Advisory Lock):依赖进程主动遵守锁规则(常用)。

  2. 强制性锁(Mandatory Lock):由内核强制限制读写(需特殊配置)。

强制锁相对效率不高,通常用建议锁。主要是使用如下API

flock() :对整个文件加锁(基于文件描述符)。

fcntl():支持对文件的某个区域加锁(更细粒度)。

lockf():基于 fcntl() 的封装,简化文件锁操作。

第一个例子:多进程安全写入日志文件

目标:确保多个进程按顺序写入日志,避免行交错。

代码实现:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <time.h>

void write_log(const char *message) {
    int fd = open("app.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
    if (fd == -1) {
        perror("open log failed");
        exit(1);
    }

    // 加排他锁(阻塞直到获得锁)
    struct flock lock = {
        .l_type = F_WRLCK,
        .l_whence = SEEK_SET,
        .l_start = 0,
        .l_len = 0// 锁住整个文件
    };
    fcntl(fd, F_SETLKW, &lock);

    // 写入日志(带时间戳)
    time_t now = time(NULL);
    char buffer[256];
    snprintf(buffer, sizeof(buffer), "[%ld] %s\n", now, message);
    write(fd, buffer, strlen(buffer));

    // 释放锁
    lock.l_type = F_UNLCK;
    fcntl(fd, F_SETLK, &lock);

    close(fd);
}

int main() {
    // 模拟多进程写入(编译后启动多个进程测试)
    write_log("Process started");
    sleep(1);  // 模拟业务逻辑耗时
    write_log("Process finished");
    return0;
}

每次写日志前通过 F_WRLCK 获取排他锁,确保原子性, F_SETLKW 会阻塞进程直到锁被释放,其实看起来与线程同步中的读写锁类似。

第二个例子:单例进程锁文件实现

目标:通过锁文件 /var/run/myapp.pid 防止重复启动。

代码实现:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

void enforce_singleton() {
    constchar *lockfile = "/var/run/myapp.pid";
    int fd = open(lockfile, O_WRONLY | O_CREAT, 0644);
    if (fd == -1) {
        perror("open lockfile failed");
        exit(1);
    }

    // 尝试加建议性锁(非阻塞)
    struct flock lock = {
        .l_type = F_WRLCK,
        .l_whence = SEEK_SET,
        .l_start = 0,
        .l_len = 0
    };
    if (fcntl(fd, F_SETLK, &lock) == -1) {
        if (errno == EAGAIN || errno == EACCES) {
            fprintf(stderr, "Error: Another instance is already running.\n");
            exit(1);
        } else {
            perror("fcntl failed");
            exit(1);
        }
    }

    // 写入当前进程ID
    char pid_str[16];
    snprintf(pid_str, sizeof(pid_str), "%d\n", getpid());
    ftruncate(fd, 0);  // 清空文件内容
    write(fd, pid_str, strlen(pid_str));
}

int main() {
    enforce_singleton();
    printf("Service started. PID: %d\n", getpid());
    while (1) {
        sleep(60);  // 模拟守护进程逻辑
    }
    return0;
}

通过启动时尝试对锁文件加排他锁,若失败则退出,锁文件内容为进程PID,便于问题排查,与设计模式中的单例模式类似。

你还可以通过lslocks命令来查看系统中当前的文件锁,值得注意的是锁的粒度(全局锁 vs 区域锁)和死锁风险。

相关推荐
wj3055853785 小时前
课程 9:模型测试记录与 Prompt 策略
linux·人工智能·python·comfyui
吃好睡好便好6 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
abigriver6 小时前
打造 Linux 离线大模型级语音输入法:Whisper.cpp + 3090 显卡加速与 Rime 中英混输终极调优指南
linux·运维·whisper
仰泳之鹅6 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
wangqiaowq6 小时前
windows下nginx的安装
linux·服务器·前端
YYRAN_ZZU7 小时前
Petalinux新建自动脚本启动
linux
charlie1145141917 小时前
嵌入式Linux驱动开发pinctrl篇(1)——从寄存器到子系统:驱动演进之路
linux·运维·驱动开发
于小猿Sup7 小时前
VMware在Ubuntu22.04驱动Livox Mid360s
linux·c++·嵌入式硬件·自动驾驶
cen__y8 小时前
Linux12(Git01)
linux·运维·服务器·c语言·开发语言·git
x_yeyue8 小时前
三角形数
笔记·算法·数论·组合数学