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 区域锁)和死锁风险。

相关推荐
倒头就睡的小比特2 天前
算法竞赛C++常用的STL
c++·算法
小羊没烦恼!2 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
未济2 天前
linux 配置环境变量
linux
傲世仙尊2 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
虎头金猫2 天前
4K 视频总卡在公网带宽?用 N1 + OpenList 把网盘播放链路重新理顺
运维·服务器·网络·python·容器·beautifulsoup·pandas
猎头南楼2 天前
知识社区推荐系统实践:新用户冷启动与长短期兴趣建模的挑战 资深推荐算法工程师
人工智能·深度学习·算法·机器学习
_艾伦 耶格尔.2 天前
进程间通信
linux
旖旎夜光2 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
wzdark2 天前
大规模并行计算中的负载均衡算法研究4
算法
Liuqy-052 天前
Linux IO编程——静态库、动态库
linux