我们在 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"); // 文件为空,写入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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/121757.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据可视化报表分享:区域管理驾驶舱

在零售数据分析中&#xff0c;区域管理驾驶舱报表是用来分析企业运营数据&#xff0c;以制定销售策略和提高利润。因此这张报表需要整合大量数据&#xff0c;数据整合、分析、指标计算的工作量极大&#xff0c;在讲究高效率、高度及时性的大数据时代&#xff0c;BI数据可视化分…

Linux--安装与配置虚拟机及虚拟机服务器坏境配置与连接---超详细教学

一&#xff0c;操作系统介绍 1.1.什么是操作系统 操作系统&#xff08;Operating System&#xff0c;简称OS&#xff09;是一种系统软件&#xff0c;它是计算机硬件和应用软件之间的桥梁。它管理计算机的硬件和软件资源&#xff0c;为应用程序提供接口和服务&#xff0c;并协调…

9、定义错误页

在layouts目录下新建error.vue&#xff0c;可以通过layout函数使用布局文件&#xff0c;通过props: [“error”]能拿到错误信息对象。 <template><div>{{ error.statusCode }}: {{ error.message }}</div> </template><script> export default {…

vue3 源码解析(2)— ref、toRef、toRefs、shallowRef 响应式的实现

前言 vue3 源码解析&#xff08;1&#xff09;— reactive 响应式实现 介绍完 reactive 之后还有另一个很重要的响应式API&#xff0c;其中包括 ref、toRef、toRefs 和 shallowRef。这些API在vue3中起着至关重要的作用&#xff0c;它们帮助我们更好地管理和跟踪响应式数据的变…

漏洞复现-dedecms文件上传(CVE-2019-8933)

dedecms文件上传_CVE-2019-8933 漏洞信息 Desdev DedeCMS 5.7SP2版本中存在安全漏洞CVE-2019-8933文件上传漏洞 描述 ​ Desdev DedeCMS&#xff08;织梦内容管理系统&#xff09;是中国卓卓网络&#xff08;Desdev&#xff09;公司的一套基于PHP的开源内容管理系统&#x…

【面试经典150 | 链表】合并两个有序链表

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;递归方法二&#xff1a;迭代 写在最后 Tag 【递归】【迭代】【链表】 题目来源 21. 合并两个有序链表 题目解读 合并两个有序链表。 解题思路 一种朴素的想法是将两个链表中的值存入到数组中&#xff0c;然后对数组…

轻量级仿 Spring Boot=嵌入式 Tomcat+Spring MVC

啥&#xff1f;Spring Boot 不用&#xff1f;——对。就只是使用 Spring MVC Embedded Tomcat&#xff0c;而不用 Boot。为啥&#xff1f;——因为 Boot 太重了&#xff1a;&#xff09; 那是反智吗&#xff1f;Spring Boot 好好的就只是因为太重就不用&#xff1f;——稍安勿…

有一个带头结点的单链表L,设计一个算法使其元素递增有序

有一个带头结点的单链表L&#xff0c;设计一个算法使其元素递增有序 代码思路&#xff1a; 我这里懒得搞那个指针了&#xff0c;直接遍历一遍链表&#xff0c;把链表的元素复制到数组arr里面 对数组A进行一下排序&#xff0c;排完之后再把元素复制到L里面。 至于排序你用啥算…

运维 | 使用 Docker 安装 Jenkins | Jenkins

运维 | 使用 Docker 安装 Jenkins | Jenkins 前言 本期内容主要是为了学习如何通过 Docker 安装Jenkins&#xff0c;仅作为记录与参考&#xff0c;希望对大家有所帮助。 准备工作 系统&#xff1a;CentOS 7.9配置&#xff1a;4c8g 快速安装 下面以 Docker 方式安装 Jenkin…

第2篇 机器学习基础 —(2)分类和回归

前言&#xff1a;Hello大家好&#xff0c;我是小哥谈。机器学习中的分类和回归都是监督学习的问题。分类问题的目标是将输入数据分为不同的类别&#xff0c;而回归问题的目标是预测一个连续的数值。分类问题输出的是物体所属的类别&#xff0c;而回归问题输出的是数值。本节课就…

【Matlab2016】Matlab中文版的下载、安装、激活(不建议安装过高版本!!)

这里写目录标题 首先双击R2016_win64.iso加载镜像文件双击setup.exe开始安装选择使用文件密钥安装填入密钥修改安装路径并记住此路径建议全部勾选等待安装完成 激活复制补丁到matlab路径下 创建快捷方式进入bin目录&#xff0c;找到matlab.exe 安装包 首先双击R2016_win64.iso加…

网络搭建和运维的基础题目

服务部分&#xff08;linux&#xff09; 实操部分 1.在任意文件夹下面创建形如 A/B/C/D 格式的文件夹系列。 [rootlocalhost ~]# mkdir -p A/B/C/D 2.在创建好的文件夹下面&#xff0c;A/B/C/D &#xff0c;里面创建文本文件 mkdir.txt [rootlocalhost ~]# cd A/B/C/D [r…

[17]JAVAEE-HTTP协议

目录 一、什么是HTTP协议 什么时候会用到HTTP协议&#xff1f; HTTP协议的工作流程 二、HTTP的报文格式 抓包 HTTP请求报文格式 1.首行 2.header 常见键值对&#xff1a; 3.空行 4.正文&#xff08;body&#xff09;&#xff08;有的时候可以没有&#xff09; HTTP…

Python爬虫防止被封的方法:动态代理ip

目录 前言 一、为什么需要使用动态IP代理 1.网站反爬虫机制 2.突破本地IP限制 3.获取更多数据 二、Python爬虫动态IP代理的实现方法 1.使用第三方库 2.使用爬虫框架 三、预防被封的方法 1.代理池管理 2.请求间隔设置 3.使用多个代理 总结 前言 在进行网站爬取时&…

网络基础-3

路由开销 一条路由的开销时指到达这条路由的目的地/掩码需要付出的带价值。同一种路由协议发现有多条路由可以到达同一目的地/掩码时&#xff0c;将优选开销最小的路由&#xff0c;即只把开销最小的路由加入进本协议的路由表中。 路由协议 内部网关协议&#xff08;IGP&…

网络协议--TFTP:简单文件传送协议

15.1 引言 TFTP(Trivial File Transfer Protocol)即简单文件传送协议&#xff0c;最初打算用于引导无盘系统&#xff08;通常是工作站或X终端&#xff09;。和将在第27章介绍的使用TCP的文件传送协议&#xff08;FTP&#xff09;不同&#xff0c;为了保持简单和短小&#xff0…

C++STL----list的模拟实现

文章目录 list模拟实现的大致框架节点类的模拟实现迭代器类的模拟实现迭代器类存在的意义迭代器类的模板参数说明运算符的重载--运算符的重载&#xff01;与运算符的重载*运算符的重载->运算符的重载 list的模拟实现默认成员函数迭代器相关函数元素修改相关函数front和backi…

莫名其妙el-table不显示问题

完全复制element-ui中table代码&#xff0c;发现表格仍然不显示&#xff0c;看别人都说让降低版本&#xff0c;可我不想降低啊&#xff0c;不然其他组件有可能用不了&#xff0c;后来发现可以通过配置vite.config.js alias: {: path.resolve(__dirname, src),vue: vue/dist/vue…

Python实现双目标定、畸变矫正、立体矫正

一&#xff0c;双目标定、畸变矫正、立体矫正的作用 双目目标定&#xff1a; 3D重建和测距&#xff1a;通过双目目标定&#xff0c;您可以确定两个摄像头之间的相对位置和朝向&#xff0c;从而能够根据视差信息计算物体的深度&#xff0c;进行三维重建和测距。姿态估计&#xf…

CVE-2022-22963 Spring Cloud Function SpEL命令注入

一、简介 Spring Cloud Function 是基于 Spring Boot的函数计算框架。该项目致力于促进函数为主的开发单元&#xff0c;它抽象出所有传输细节和基础架构&#xff0c;并提供一个通用的模型&#xff0c;用于在各种平台上部署基于函数的软件。在Spring Cloud Function相关版本&am…