嵌入式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…

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

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

【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…

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

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

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

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

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

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

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

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

Leetcode:最长公共前缀

题目链接&#xff1a;14. 最长公共前缀 - 力扣&#xff08;LeetCode&#xff09; 普通版本&#xff08;横向扫描&#xff09; 主旨&#xff1a;用第一个字符串与后续的每个字符串进行比较&#xff0c;先获取S1和S2的最长公共前缀&#xff0c;然后将该次比较获得的最长公共前缀…

python中如何使用密码字典

使用itertools循环迭代的模块来实现生成密码字典&#xff0c;用这个模块可以省不少事。 首先要调用itertools。 import itertools as its 然后将字典需要的元素&#xff08;或者说是关键字&#xff09;赋给word变量。 我们这里假设密码是纯数字&#xff0c;所以元素就是12345…

创新指南|2024企业如何开启生成式AI创新?从5大应用场景和6步抓手

想要了解如何采用生成式AI来提高企业效率和竞争力&#xff1f;本指南将介绍如何采用生成式AI来实现数字化转型&#xff0c;并打造智能化商业模式。从5大应用场景和6大步骤切入&#xff0c;让您了解如何开启生成式AI创新。立即连线创新专家咨询或观看创新战略方案视频进一步了解…

test2042

语义边缘检测和语义分割的区别 语义边缘检测&语义分割 Semantic Edge Detection vs. Semantic Segmentation 区别difference 任务目标 Task Objective 语义边缘检测 Semantic Edge Detection 识别图像中不同物体之间的边界线或轮廓及语义类别 Identifying the boundaries …

2024如何优化SEO?

在2024年的今天&#xff0c;要问我会如何优化seo&#xff0c;我会专注于几个关键的方面。首先&#xff0c;随着AI内容生成技术的发展&#xff0c;我会利用这些工具来帮助创建或优化我的网站内容&#xff0c;但是&#xff0c;随着谷歌3月份的算法更新&#xff0c;纯粹的ai内容可…

无法访问内网怎么办?

许多用户在日常生活和工作中&#xff0c;经常需要进行远程连接和访问内网的需求。出于各种原因&#xff0c;有时我们会遇到无法访问内网的问题。本文将从可能的原因和解决方案的角度来探讨此问题。 原因分析 网络设置问题: 在一些情况下&#xff0c;我们无法访问内网可能是因为…

分层存储的图片的3d显示

分层存储的图片叠层成为3d&#xff0c;并显示。 文件夹D:\mask内的分层存储的图像文件mask_1.PNG至mask_12.PNG&#xff1a; 1、显示为3d点云&#xff1a; import open3d as o3d import numpy as np from PIL import Imagedef images2point_cloud(paths, layer_height):point…

(九)Spring教程——ApplicationContext中Bean的生命周期

1.前言 ApplicationContext中Bean的生命周期和BeanFactory中的生命周期类似&#xff0c;不同的是&#xff0c;如果Bean实现了org.springframework.context.ApplicationContextAware接口&#xff0c;则会增加一个调用该接口方法setApplicationContext()的步骤。 此外&#xff0c…

香橙派 Orange AIpro 测评记录视频硬件解码

香橙派 Orange AIpro 测评记录视频硬件解码 香橙派官网&#xff1a;http://www.orangepi.cn/ 收到了一块Orange Pi AIpro开发板&#xff0c;记录一下我的测评~测评简介如下&#xff1a;1.连接网络2.安装流媒体进行硬件解码测试3.安装IO测试 简介 Orange Pi AI Pro 是香橙派联合…

0基础学习区块链技术——链之间数据同步样例

我们可以在https://blockchaindemo.io/体验这个过程。 创建区块 默认第一个链叫Satoshi(中本聪)。链上第一个区块叫“创世区块”——Genesis Block。后面我们会看到创建的第二条链第一个区块也是如此。 新增链 新创建的链叫Debby。默认上面有一个创世区块。 然后我们让这…