嵌入式Linux系统编程 — 1.4 原子操作与竞争冒险

目录

1 竞争冒险

1.1 竞争冒险由来

1.2 竞争冒险理解

2 原子操作 

2.1 O_APPEND 实现原子操作

2.2 pread()和 pwrite()

2.3 O_EXCL 标志创建文件


1 竞争冒险

1.1 竞争冒险由来

Linux 是一个支持多任务和多用户同时运行的操作系统,它允许多个进程同时执行。在这种环境下,可能会有多个进程同时对同一个文件执行输入输出(IO)操作,使得该文件成为这些进程共享的资源。与直接在硬件上运行的裸机编程不同,Linux 操作系统提供了进程管理和多任务处理的功能。在裸机编程中,通常不会有进程和多任务的概念,然而在 Linux 系统中,需要特别注意在多进程环境中可能发生的竞争冒险,因为不同的进程可能会同时试图访问和修改共享资源,这可能导致数据不一致或程序行为异常。

1.2 竞争冒险理解

下面代码是一个典型的多线程竞争冒险场景。程序中定义了一个共享的整型变量shared_counter,并将其声明为volatile以防止编译器进行优化。increment函数是线程执行的主体,它在每个线程中运行1000次,每次循环中调用usleep(10)来模拟CPU密集型操作,然后对shared_counter进行递增。在main函数中,创建了10个线程,每个线程都执行increment函数。程序使用pthread_create来创建线程,并通过pthread_join等待所有线程完成。最后,程序打印出预期的shared_counter值(即NUM_THREADS * 1000),以及实际的shared_counter值。由于shared_counter的递增操作不是原子的,并且没有使用同步机制来防止多个线程同时修改它,因此实际值可能会小于预期值,展示了竞争冒险的效果。

#include <pthread.h>
#include <stdio.h>
#include <unistd.h> // 引入usleep函数// 共享资源
volatile int shared_counter = 0; // 使用volatile关键字防止编译器优化// 线程函数
void* increment(void* arg) {for (int i = 0; i < 1000; ++i) {// 模拟一些CPU密集型的操作usleep(10); // 让线程暂停一小段时间,增加线程调度的不确定性// 非原子操作,可能导致竞争冒险shared_counter++;}return NULL;
}int main() {const int NUM_THREADS = 10; // 增加线程数量pthread_t threads[NUM_THREADS];// 创建多个线程for (int i = 0; i < NUM_THREADS; ++i) {if (pthread_create(&threads[i], NULL, increment, NULL) != 0) {perror("Error creating thread");return 1;}}// 等待所有线程结束for (int i = 0; i < NUM_THREADS; ++i) {pthread_join(threads[i], NULL);}// 输出共享变量的值printf("Expected value: %d\nActual value: %d\n", NUM_THREADS * 1000, shared_counter);return 0;
}

在这个修改后的程序中,每个线程只执行1000次递增操作,同时在每次递增操作前后添加了usleep(10),以模拟一些CPU密集型的操作并增加线程调度的不确定性。由于竞争冒险,shared_counter的实际值很可能小于预期的值(在这个例子中是10000)。通过运行这个程序多次,你应该能够观察到不同的输出值,这些值通常小于预期值,从而演示了竞争冒险的影响。运行的结果如下:

2 原子操作 

在Linux系统中,原子操作是一种确保在多线程环境下对共享数据进行安全访问的技术。原子操作的关键在于它们是不可分割的最小执行单元,即在执行过程中不会被其他线程中断。这使得原子操作成为避免竞争冒险(race conditions)和确保数据一致性的重要工具。

使用原子操作时,程序员不需要手动管理锁,因为原子操作本身提供了必要的同步。这简化了编程模型,并有助于避免死锁和优先级反转等并发问题。

2.1 O_APPEND 实现原子操作

O_APPEND 是一个在 Linux 系统中打开文件时使用的文件状态标志,它确保所有写操作都追加到文件末尾。虽然 O_APPEND 本身并不直接实现原子操作,但它确实保证了写操作的原子性,因为它防止了写入位置在多个进程或线程之间的竞争。

当多个进程或线程打开同一个文件并指定 O_APPEND 标志时,内核会负责管理文件写入指针,确保每个写操作都从文件的当前末尾开始。这意味着即使多个进程或线程同时写入同一个文件,每个写入操作也会被追加到文件的末尾,而不会相互干扰。

这里是一个简单的例子,演示如何使用 O_APPEND 来确保写操作的原子性:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main() {int fd = open("example.txt", O_WRONLY | O_APPEND);if (fd == -1) {perror("Failed to open file");return 1;}const char* message = "This is a test message\n";if (write(fd, message, strlen(message)) == -1) {perror("Failed to write to file");close(fd);return 1;}close(fd);return 0;
}

在这个例子中,使用 open 函数打开一个名为 "example.txt" 的文件,并指定 O_WRONLY(只写模式)和 O_APPEND 标志。这确保了所有的 write 调用都会将数据追加到文件末尾。即使多个进程同时运行这段代码,每个进程的写操作也会被顺序地追加到文件末尾,而不会覆盖其他进程的写入内容。

需要注意的是,虽然 O_APPEND 确保了写入操作的顺序性,但它并不保证数据的完整性或写入操作的原子性。例如,如果一个写操作被分割成多个部分,O_APPEND 不能保证这些部分会连续地写入文件。对于需要确保数据完整性的场景,可能需要使用其他同步机制,如互斥锁(mutexes)或文件锁(file locking)。

2.2 pread()和 pwrite()

这两个函数需要包含头文件<unistd.h>。pread()和 pwrite()都是系统调用,与 read()、 write()函数的作用一样,用于读取和写入数据。区别在于,pread()和 pwrite()可用于实现原子操作,调用 pread 函数或 pwrite 函数可传入一个位置偏移量 offset 参数,用于指定文件当前读或写的位置偏移量,所以调用 pread 相当于调用 lseek 后再调用 read;同理,调用 pwrite相当于调用 lseek 后再调用 write。 所以可知, 使用 pread 或 pwrite 函数不需要使用 lseek 来调整当前位置偏移量,并会将“移动当前位置偏移量、读或写”这两步操作组成一个原子操作。

(1)pread()函数

pread() 用于从文件中读取数据,而不改变文件的当前读指针。这意味着每次调用 pread(),都会从指定的文件位置开始读取数据,而不影响其他读/写操作。

ssize_t pread(int fd, void *buf, size_t count, off_t offset);

参数说明:

  • fd: 文件描述符,表示要读取的文件。
  • buf: 指向一个缓冲区的指针,用于存储从文件中读取的数据。
  • count: 要读取的字节数。
  • offset: 文件中的偏移量,从这个位置开始读取数据。

(2)pwrite()函数

pwrite()pread() 相对应,用于在文件中的指定位置写入数据,而不改变文件的当前写指针。

ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);

参数说明:

  • fd: 文件描述符,表示要写入的文件。
  • buf: 指向要写入的数据的缓冲区的指针。
  • count: 要写入的字节数。
  • offset: 文件中的偏移量,从这个位置开始写入数据。

 (3)示例代码

下面的示例代码展示了如何使用 pread()pwrite() 函数:

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main() {int fd;const char *data = "Sample data";char buffer[20];ssize_t bytes_read, bytes_written;// 创建或打开文件fd = open("testfile.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}// 使用 pwrite() 写入数据bytes_written = pwrite(fd, data, strlen(data), 0);if (bytes_written == -1) {perror("pwrite");close(fd);exit(EXIT_FAILURE);}// 移动文件指针到文件中间的某个位置if (lseek(fd, 5, SEEK_SET) == -1) {perror("lseek");close(fd);exit(EXIT_FAILURE);}// 使用 pread() 从指定位置读取数据bytes_read = pread(fd, buffer, sizeof(buffer), 5);if (bytes_read == -1) {perror("pread");close(fd);exit(EXIT_FAILURE);}buffer[bytes_read] = '\0'; // 确保字符串终止// 打印读取的数据printf("Read data: %s\n", buffer);// 关闭文件close(fd);return 0;
}

代码中首先打开(或创建)一个名为 testfile.txt 的文件用于读写。然后,我们使用 pwrite() 在文件开头写入一个字符串。接着,我们使用 lseek() 将文件指针移动到文件的第5个字节,然后使用 pread() 从这个位置读取数据到缓冲区中。最后,我们打印出读取的数据,并关闭文件。运行的结果如下:

2.3 O_EXCL 标志创建文件

创建文件中存在竞争状态,例如进程 A 和进程 B 都要去打开同一个文件、并且此文件还不存在。进程 A 当前正 在运行状态、进程 B 处于等待状态,进程 A 首先调用 open("./file", O_RDWR)函数尝试去打开文件,结果返 回错误,也就是调用 open 失败;接着进程 A 时间片耗尽、进程 B 运行,同样进程 B 调用 open("./file",O_RDWR)尝试打开文件,结果也失败,接着进程 B 再次调用 open("./file", O_RDWR | O_CREAT, ...)创建此 文件,这一次 open 执行成功,文件创建成功;接着进程 B 时间片耗尽、进程 A 继续运行,进程 A 也调用open("./file", O_RDWR | O_CREAT, ...)创建文件,函数执行成功。

创建文件中存在的竞争状态

从上面的示例可知,进程 A 和进程 B 都会创建出同一个文件,同一个文件被创建两次这是不允许的,那如何规避这样的问题呢? 那就是通过使用 O_EXCL 标志, 当 open 函数中同时指定了 O_EXCL 和O_CREAT 标志,如果要打开的文件已经存在,则 open 返回错误;如果指定的文件不存在,则创建这个文件,这里就提供了一种机制,保证进程是打开文件的创建者,将“判断文件是否存在、创建文件”这两个步骤合成为一个原子操作。
 

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

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

相关文章

kube-promethesu新增k8s组件监控(etcd\kube-controller-manage\kube-scheduler)

我们的k8s集群是二进制部署 一、prometheus添加自定义监控与告警&#xff08;etcd&#xff09; 1、步骤及注意事项&#xff08;前提&#xff0c;部署参考部署篇&#xff09; 1.1 一般etcd集群会开启HTTPS认证&#xff0c;因此访问etcd需要对应的证书 1.2 使用证书创建etcd的…

Verilog实战学习到RiscV - 3 : ICEStick 评估板点灯

收到 ICESTICK 评估板后还没好好玩。先来点个灯&#xff0c;正好把之前介绍过的工具链串起来用一下。 代码 Verilog代码只有一个顶层模块top.v&#xff0c;定义如下&#xff1a; module top(output wire D1,output wire D2,output wire D3,output wire D4,output wire D5);a…

王安石传-梁启超

国史氏曰&#xff1a;甚矣&#xff0c;知人论世之不易易也。以余所见宋太傅荆国王文公安石&#xff0c;其德量汪然若千顷之陂&#xff0c;其气节岳然若万仞之壁&#xff0c;其学术集九流之粹&#xff0c;其文章起八代之衰&#xff0c;其所设施之事功&#xff0c;适应于时代之要…

云手机定位切换,带来的不只是便利

当我们利用云手机的定位切换时&#xff0c;首先感受到的确实是极大的便利。 我们就像是拥有了瞬间移动的超能力&#xff0c;可以自由地在不同城市、甚至不同国家的虚拟场景中穿梭。无论是为了更精准地获取当地的信息&#xff0c;比如实时的交通状况、特色店铺等&#xff0c;还…

solana 通过助记码 恢复 path 私钥 导入相关插件钱包

solana 插件钱包 支持助记码 &#xff0c;私钥等方式导入&#xff0c;但区别于metamask 每次只能导入一个path 账户&#xff0c;头疼&#xff01; 上代码 import { Keypair } from "solana/web3.js"; import { HDKey } from "micro-ed25519-hdkey"; impor…

【CT】LeetCode手撕—3. 无重复字符的最长子串

目录 题目1- 思路1-1 模式1&#xff1a;涉及去重判断1-2 模式2&#xff1a;遍历字符串区间 2- 题解⭐无重复字符的最长子串——题解思路 3- ACM实现 原题链接&#xff1a;3. 无重复字符的最长子串 题目 无重复字符的最长子串 给定一个字符串 s &#xff0c;请你找出其中不含有…

裸芯片绑定键合电阻器

EAK厚膜阻芯与有源裸片键合在一起封装 一般说明 精密引线键合电阻具有极高的稳定性和可靠性。电阻器经过激光微调&#xff0c;达到严格的公差。可定制值和该值的唯一标记&#xff0c;非常适合但不限于混合电路应用。 潜在作用&#xff1a; 医用植入式 军事/国防 混合设计 …

搭建chattts应用,做文字转语音

下载代码 git clone https://github.com/2noise/ChatTTS.git下载endpoint并上传&#xff1a; https://huggingface.co/2Noise/ChatTTS/tree/main 将上面下载的文件上传到服务器上 修改webui.py 更改为本地模型地址 import os import random import argparseimport torch i…

dpdk-19.11 arm64 环境适配 Mellanox CX4 网卡

环境信息 cpu: arm64 架构 dpdk 版本&#xff1a;19.11 glibc 版本&#xff1a;2.17 网卡型号&#xff1a; Mellanox CX4 网卡&#xff0c;详细 pci 信息如下&#xff1a; 02:00.0 Ethernet controller: Mellanox Technologies MT27710 Family [ConnectX-4 Lx] 02:00.1 Ether…

一份最基础的gitlab-ci文件,其中就maven的build和test

说明的gitlab-ci.yml文件&#xff0c;包含注释解释每个部分的功能&#xff1a; # 定义流水线的各个阶段&#xff0c;包含两个阶段&#xff1a;build 和 test stages:- build- test# 设置Maven的环境变量&#xff0c;指定本地仓库位置 variables:MAVEN_OPTS: "-Dmaven.rep…

java-String 比较

在 Java 中&#xff0c;String 类的对象用于表示字符串。字符串比较是编程中常见的需求&#xff0c;比如判断两个字符串是否相等&#xff0c;或者比较两个字符串的大小等。Java 提供了多种方法来比较 String 对象。 ### 字符串比较的方式 #### 1. 使用 运算符 运算符用于比较…

投资少见效快产量高-UMLChina建模知识竞赛第5赛季第13轮

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 参考潘加宇在《软件方法》和UMLChina公众号文章中发表的内容作答。在本文下留言回答。 只要最先答对前3题&#xff0c;即可获得本轮优胜。 如果有第4题&#xff0c;第4题为附加题&am…

2024上海城博会:以绿色·节能·创新为主题,推动长三角一体化发展

2024上海城博会&#xff0c;作为“世界城市日”的重要主题活动之一&#xff0c;再次将全球的目光聚焦于这座东方明珠&#xff0c;展现了其在城市建设与管理领域的卓越成就。本次城博会由联合国人居署、上海市住房和城乡建设管理委员会联合主办&#xff0c;上海市绿色建筑协会承…

codereview时通常需要关注哪些

在团队成员之间互相进行代码审查&#xff08;codereview&#xff09;时&#xff0c;通常可以从以下几个方面来确保代码的质量和可维护性&#xff1a; 代码结构和格式&#xff1a; 检查代码是否遵循了项目约定的编码规范和风格指南。确保代码具有良好的可读性&#xff0c;比如合…

Qt 判断文件夹是否存在,不存在则创建

int CHttpFileRequest::CreatDownloadFile(QString path,QString fileName) {QDir mDir;if(!mDir.exists(path)){mDir.mkpath(path);}return 0; }

C语言字符函数与字符串函数详解

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 [TOC](文章目录) 前言一、针对单个字符的C语言库中的函数&#xff08;一&#xff09;、字符分类函数&#xff08;二&#xff09;、字符转换函数 二、针对字符串的C语…

opengauss创建和管理分区表

创建和管理分区表 背景信息 openGauss数据库支持的分区表为范围分区表、列表分区表、哈希分区表。 范围分区表&#xff1a;将数据基于范围映射到每一个分区&#xff0c;这个范围是由创建分区表时指定的分区键决定的。这种分区方式是最为常用的&#xff0c;并且分区键经常采用日…

Springboot 开发-- 集成 Activiti 7 流程引擎

引言 Activiti 7是一款遵循BPMN 2.0标准的开源工作流引擎&#xff0c;旨在为企业提供灵活、可扩展的流程管理功能。它支持图形化的流程设计、丰富的API接口、强大的执行引擎和完善的监控报表&#xff0c;帮助企业实现业务流程的自动化、规范化和智能化。本文将为您详细介绍 Ac…

Spring (39)微服务架构

微服务架构是一种将单个应用程序作为一套小服务开发的方法&#xff0c;每个服务运行在其自身的进程中&#xff0c;并且通常围绕业务能力构建&#xff0c;使用轻量级的通信机制&#xff08;常见的如HTTP RESTful API&#xff09;。这些服务是自包含的&#xff0c;因为它们分别持…

kubesphere报错

1.安装过程报错unable to sign certificate: must specify a CommonName [rootnode1 ~]# ./kk init registry -f config-sample.yaml -a kubesphere.tar.gz _ __ _ _ __ | | / / | | | | / / | |/ / _ _| |__ ___| |/…