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"); // 文件为空,写入0fflush(file); // 刷新文件缓冲区,确保写入文件}fseek(file, 0, SEEK_SET); // 将文件指针移动到文件开头int num;fscanf(file, "%d", &num); // 读取文件中的整数printf("num = %d\n", num);sleep(2);while(count--) {num++; // 将整数加1fseek(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 + 0lock.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"); // 文件为空,写入0fflush(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++; // 将整数加1fseek(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();// 关键区域 ------------ endfclose(file); // 关闭文件return 0;
}
再次编译执行,发现最终文本文件的整数是 100,说明加锁确实有效防止了 race condition