我们在 Linux 环境中用 C 编程时,如果对文件读写,Linux 会自动给文件加锁嘛?以及怎么加文件锁?

task1: 验证Linux不会自动给文件加锁

先说结论,结论是不会

我写了一个这样的程序

#include <stdio.h>
#include <unistd.h>

int main() {
    const char* pathname = "your_file_pathname.txt";
    FILE* file = NULL;
    int count = 100;

    if(access(pathname, F_OK) == 0) {
        file = fopen(pathname, "r+"); 
        printf("open in r+ mode\n");
    }
    else {
        file = fopen(pathname, "w+"); 
        printf("open in w+ mode\n");
    }

    if (file == NULL) {
        printf("无法打开文件\n");
        return 1;
    }

    fseek(file, 0, SEEK_END); // 将文件指针移动到文件末尾
    long file_size = ftell(file); // 获取文件大小

    if (file_size == 0) {
        fprintf(file, "0\n"); // 文件为空,写入0
        fflush(file); // 刷新文件缓冲区,确保写入文件
    }
    

    fseek(file, 0, SEEK_SET); // 将文件指针移动到文件开头
    int num;
    fscanf(file, "%d", &num); // 读取文件中的整数
    printf("num = %d\n", num);
    sleep(2);

    while(count--) {
        num++; // 将整数加1
        fseek(file, 0, SEEK_SET); // 将文件指针移动到文件开头
        fprintf(file, "%d\n", num); // 将更新后的整数写回文件
        fflush(file); // 刷新文件缓冲区,确保写入文件
        fseek(file, 0, SEEK_SET); // 将文件指针移动到文件开头
        fscanf(file, "%d", &num); // 读取文件中的整数
        printf("num = %d\n", num);
        sleep(2);
    }

    fclose(file); // 关闭文件

    return 0;
}

上面这个程序会读取文件中的数字,然后给数字+1,再写回文件

这个程序里没有给文件加锁,我同时运行了 8 个这样的程序,最后的 result file 里的数字是 127,而非 800,说明 Linux 本身并不会给文件加锁

task2: 如何手动给文件加锁?

首先根据 ChatGPT,我们可以获得如下代码(经过部分注释和修改,可以根据注释理解源码):

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int fd;

    // 打开文件
    fd = open("file.txt", O_WRONLY);
    if (fd == -1) {
        perror("open");
        exit(1);
    }

    // 设置文件锁
    // 我们定义了一个struct flock结构体来描述文件锁的属性,包括锁的类型、起始位置和长度。
    struct flock lock;
    lock.l_type = F_WRLCK;   // 写锁
    // short int l_whence;	/* Where `l_start' is relative to (like `lseek').  */
    // __off_t l_start;	/* Offset where the lock begins.  */
    // l_whence 和 l_start 是共同指定锁的起始位置的
    lock.l_whence = SEEK_SET;
    lock.l_start = 0;
    // __off_t l_len;	/* Size of the locked area; zero means until EOF.  */
    lock.l_len = 0;          // 锁定整个文件

    // 接下来,我们使用fcntl函数来获取文件锁,使用F_SETLKW标志表示在获取锁时阻塞进程,直到锁可用。
    // #  define F_SETLKW	7	/* Set record locking info (blocking).  */
    if (fcntl(fd, F_SETLKW, &lock) == -1) {
        perror("fcntl");
        exit(1);
    }

    // 在文件中写入数据 NOTE: 关键区域
    // ...

    // 在写入数据完成后,我们再次使用fcntl函数来释放文件锁,使用F_SETLK标志表示释放锁。
    // # define F_RDLCK		0	/* Read lock.  */
    // # define F_WRLCK		1	/* Write lock.  */
    // # define F_UNLCK		2	/* Remove lock.  */
    // 根据手册来看,F_SETLKW 是阻塞式获取/释放锁,F_SETLK是非阻塞获取/释放锁
    lock.l_type = F_UNLCK;
    if (fcntl(fd, F_SETLK, &lock) == -1) {
        perror("fcntl");
        exit(1);
    }

    // 关闭文件
    close(fd);

    return 0;
}

根据手册阅读,如下:

在这里插入图片描述

只需要修改 lock.l_type = F_UNLCK; 就可以决定是上锁还是释放锁。F_SETLK 和 F_SETLKW 只是决定 阻塞/非阻塞 获取/释放 锁

我们做个实验看看,“写另外一个文件2,获取锁后不释放,文件1分别使用 阻塞/非阻塞 方式获取锁,看是否如手册所描述一般行为”

经过测试,当 Holding lock 的程序被强制退出时,它所持有的锁也会被强制释放

测试1:blocking 阻塞式获取锁

首先我们测试,先使用 norelease.c 文件获取锁,随后不退出

接着再使用 blocking.c 获取锁,可以发现会阻塞再这个地方

在这里插入图片描述

以下是 blocking.c 源码:
在这里插入图片描述
可以看到第36行的 printf 并没有被执行,blocking.c 验证完毕

测试2:nonblocking 非阻塞式获取锁

在这里插入图片描述
在这里插入图片描述
可以看到,非阻塞式获取锁确实是非阻塞的,它会让 fcntl() 调用返回 -1,从我们的代码来看,最终是执行了 31 行的 perror() 之后异常退出

task3: 再做一遍 task1 的实验,加上锁

首先把 task1 的 sleep 参数设置为 1,count 设置为 50,运行两个

最终文本文件中的整数是 56,确实出现了 race condition

现在加上锁,此时代码如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>

struct flock lock;
int fd;

void init_lock() {
    lock.l_whence = SEEK_SET; // 锁的起始位置是 SEEK_SET + 0
    lock.l_start = 0;
    lock.l_len = 0;          // 锁定整个文件
}

void acquire_lock() {
    lock.l_type = F_WRLCK;   // 写锁
    if (fcntl(fd, F_SETLKW, &lock) == -1) {
        perror("fcntl");
        exit(1);
    }
}

// NOTE: 释放锁不需要阻塞,因为释放锁的必须拥有锁
// NOTE: 如果释放锁的进程没有拥有锁,那说明并发写错了
void release_lock() {
    lock.l_type = F_UNLCK; // 释放锁
    if (fcntl(fd, F_SETLK, &lock) == -1) {
        perror("fcntl");
        exit(1);
    }
}

int main() {
    const char* pathname = "your_file_pathname.txt";
    FILE* file = NULL;
    int count = 50;

    if(access(pathname, F_OK) == 0) {
        file = fopen(pathname, "r+"); 
        printf("open in r+ mode\n");
    }
    else {
        file = fopen(pathname, "w+"); 
        printf("open in w+ mode\n");
    }

    if (file == NULL) {
        printf("无法打开文件\n");
        return 1;
    }

    // 获取文件描述符
    fd = fileno(file);
    // 初始化全局锁
    init_lock();

    // 关键区域 ------------ start
    // 上锁
    acquire_lock();

    fseek(file, 0, SEEK_END); // 将文件指针移动到文件末尾
    long file_size = ftell(file); // 获取文件大小

    if (file_size == 0) {
        fprintf(file, "0\n"); // 文件为空,写入0
        fflush(file); // 刷新文件缓冲区,确保写入文件
    }

    // 开锁
    release_lock();
    // 关键区域 ------------ end

    // 关键区域 ------------ start
    // 上锁
    acquire_lock();

    fseek(file, 0, SEEK_SET); // 将文件指针移动到文件开头
    int num;
    fscanf(file, "%d", &num); // 读取文件中的整数
    printf("num = %d\n", num);
    sleep(1);

    while(count--) {
        num++; // 将整数加1
        fseek(file, 0, SEEK_SET); // 将文件指针移动到文件开头
        fprintf(file, "%d\n", num); // 将更新后的整数写回文件
        fflush(file); // 刷新文件缓冲区,确保写入文件

        // 开锁
        release_lock();

        // 上锁
        acquire_lock();

        fseek(file, 0, SEEK_SET); // 将文件指针移动到文件开头
        fscanf(file, "%d", &num); // 读取文件中的整数
        printf("num = %d\n", num);
        sleep(1);
    }

    // 开锁
    release_lock();

    // 关键区域 ------------ end

    fclose(file); // 关闭文件

    return 0;
}

再次编译执行,发现最终文本文件的整数是 100,说明加锁确实有效防止了 race condition

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值