找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4589

积分

0

好友

597

主题
发表于 2 小时前 | 查看: 5| 回复: 0

项目临近收尾,我把 Linux 应用开发中遇到的一些技术点逐步做复盘。由于这是一个多进程系统,多个进程之间的资源共享问题不可避免,文件锁(File Locking)正是解决这些问题的关键机制。

1. 多进程开发中常见的三个问题

一个多进程系统里,文件访问冲突几乎不可避免,下面先看三个比较典型的场景。

1.1 日志行交错问题

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

进程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.

格式一旦错乱,日志可读性就会变得很差,后续排查问题也费劲。

1.2 配置文件更新导致损坏

若两个进程同时修改 config.json

  • 进程 A 写入前半部分内容后,进程 B 抢占了写入权。
  • 进程 B 覆盖了进程 A 的部分内容,导致文件内容不完整。

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

1.3 单例进程重复启动

守护进程(如 daemon)通常只允许一个实例运行。如果没有锁保护,用户误操作重复启动进程,就可能造成资源竞争(比如端口占用),最终导致服务不可用。

2. 文件锁的核心作用与 API

文件锁主要通过内核机制协调多进程对文件的并发访问,核心价值包括:

  • 互斥访问:同一时间仅允许一个进程修改文件。
  • 数据一致性:确保文件内容的完整性和操作原子性。
  • 进程同步:避免资源竞争导致的不可预测行为。

Linux 系统里通常有两类文件锁:

  1. 建议性锁(Advisory Lock):依赖进程主动遵守锁规则,也最常用。
  2. 强制性锁(Mandatory Lock):由内核强制限制读写,需要特殊配置。

强制性锁效率相对不高,所以实际项目通常采用建议性锁。常用 API 有:

  • flock():对整个文件加锁(基于文件描述符)。
  • fcntl():支持对文件的某个区域加锁,粒度更细。
  • lockf():基于 fcntl() 的封装,简化文件锁操作。

2.1 多进程安全写入日志文件

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

代码实现:

#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");
    return 0;
}

这里每次写日志前都通过 F_WRLCK 获取排他锁,保证写入的原子性;F_SETLKW 会阻塞进程直到锁被释放。这个思路和线程同步里的读写锁有些相似。

2.2 单例进程锁文件实现

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

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>

void enforce_singleton() {
    const char *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);  // 模拟守护进程逻辑
    }
    return 0;
}

启动时尝试对锁文件加排他锁,一旦失败就说明已有实例在运行,直接退出。锁文件里保存当前进程 PID,方便排查。这与设计模式中的单例模式思路类似。

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




上一篇:MoE混合专家模型详解:Router怎么选专家?DeepSeek V3如何做到671B总参数只激活37B
下一篇:STM32单片机C语言工厂模式实战:传感器驱动的解耦与注册机制
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-8-19 21:39 , Processed in 1.214850 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表