Linux C、C++编程之线程同步

news2025/1/11 15:09:11
【图书推荐】《Linux C与C++一线开发实践(第2版)》_linux c与c++一线开发实践pdf-CSDN博客



《Linux C与C++一线开发实践(第2版)(Linux技术丛书)》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)

多个线程可能在同一时间对同一共享资源进行操作,其结果是某个线程无法获得资源,或者会导致资源的破坏。为保证共享资源的稳定性,需要采用线程同步机制来调整多个线程的执行顺序,比如可以用一把“锁”,一旦某个线程获得了锁的拥有权,即可保证只有它(拥有锁的线程)才能对共享资源进行操作。同样地,利用这个锁,其他线程可一直处于等待状态,直到锁没有被任何线程拥有为止。

异步是当一个调用或请求发给被调用者时,调用者不用等待其结果的返回而继续当前的处理。实现异步机制的方式有多线程、中断和消息等。也就是说,多线程是实现异步的一种方式。C++11对异步的支持丝毫不弱。

并发和异步机制带来了线程间资源竞争的无序性,因此需要引入同步机制来消除这种复杂度,实现线程间正确有序地共享数据,以一致的顺序执行一组操作。

线程同步是多线程编程中的重要概念。它的基本思想是同步各个线程对资源(比如全局变量、文件)的访问。如果不对资源访问进行线程同步,则会产生资源访问冲突的问题。对于多线程程序,访问冲突的问题是很普遍的,解决的办法是引入锁(比如互斥锁、读写锁等),获得锁的线程可以完成“读-修改-写”的操作,然后释放锁给其他线程,没有获得锁的线程只能等待而不能访问共享数据,这样“读-修改-写”3步操作组成一个原子操作,要么都执行,要么都不执行,不会执行到中间被打断,也不会在其他处理器上并行做这个操作。

比如,一个线程正在读取一个全局变量,虽然读取全局变量的这条语句在C/C++源码中是一条语句,但编译为机器代码后,CPU需要用多条指令来处理这个读取变量的过程。如果这一系列指令被另一个线程打断了,也就是说CPU还没执行完读取变量的所有指令而去执行另一个线程了,另一个线程却要对这个全局变量进行修改,并将修改后的全局变量返回原先的线程,这样CPU继续执行读取变量的指令时,变量的值已经改变了,如此第一个线程的执行结果就不是预料的结果了。

我们来看一个对于多线程访问共享变量造成竞争的例子,假设增量操作分为以下3个步骤:

(1)从内存单元读入寄存器。

(2)在寄存器中进行变量值的增加。

(3)把新的值写回内存单元。

那么当两个线程对同一个变量做增加操作时,就可能出现如图9-1所示的情况。

图9-1

如果两个线程在串行操作下分别对i进行了累加,那么i的值就应该是7了,但图9-1的两个线程执行后的i值是6。因为线程B并没有等线程A做完i+1后才开始执行,而是在线程A刚刚把i从内存读入寄存器后就开始执行了,所以线程B也是在i=5的时候开始执行的,这样线程A的执行结果是6,线程B的执行结果也是6。因此,在这种没有做同步的情况下,多个线程对全局变量进行累加,最终结果是小于或等于它们的串行操作结果的。请看下例。

【例9.1】不用线程同步的多线程累加

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/time.h> 
#include <string.h>
#include <cstdlib>
 
int gcn = 0; 							// 定义一个全局变量,用于累加
  
void *thread_1(void *arg) {    			// 第一个线程
	int j;
	for (j = 0; j < 10000000; j++) {  	// 开始累加
		gcn++;
	}  
	pthread_exit((void *)0);
}

void *thread_2(void *arg) {    			// 第二个线程
	int j;
	for (j = 0; j < 10000000; j++) {  	// 开始累加
		gcn++; 
	}  
	pthread_exit((void *)0);
}
int main(void) 
{
	int j,err;
	pthread_t th1, th2;
	 
	for (j = 0; j < 10; j++)  			// 做10次
	{
		err=pthread_create(&th1, NULL, thread_1, (void *)0);// 创建第一个线程
		if (err != 0) {
			printf("create new thread error:%s\n", strerror(err));
			exit(0);
		}  
		err = pthread_create(&th2, NULL, thread_2,(void *)0);// 创建第二个线程
		if (err != 0) {
			printf("create new thread error:%s\n", strerror(err));
			exit(0);
		}  
           
		err = pthread_join(th1, NULL);  // 等待第一个线程结束
		if (err != 0) {
			printf("wait thread done error:%s\n", strerror(err));
			exit(1);
		}
		err = pthread_join(th2, NULL);  // 等待第二个线程结束
		if (err != 0) {
			printf("wait thread done error:%s\n", strerror(err));
			exit(1);
		}
		printf("gcn=%d\n", gcn);
		gcn = 0;
	}

	return 0;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

[root@localhost cpp98]# ./test
gcn=17945938
gcn=20000000
gcn=20000000
gcn=20000000
gcn=20000000
gcn=20000000
gcn=20000000
gcn=15315061
gcn=20000000
gcn=16248825

从结果中可以看到,有3次没有达到20 000 000。

上面的例子是一个语句被打断的情况,有时候还会有一个事务不能被打断。比如,一个事务需要多条语句完成,并且不可打断,如果打断的话,其他需要这个事务结果的线程则可能会得到非预料的结果。下面我们再看一个例子,有这样一个需求,伙计在卖商品时,每次卖出50元的货物就要收50元的钱,老板每隔1秒就要去清点店里的货物和金钱的总和,看总和有没有少。我们可以创建两个线程,一个线程代表伙计卖货收钱这个事务,另一个线程模拟老板验证总和的操作。抽象地讲,就是一个线程对全局变量进行写操作,另一个线程对全局变量进行读操作。

【例9.2】不用线程同步的卖货程序

(1)打开Visual Studio Code,新建一个test.cpp文件,在test.cpp中输入代码:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
 
int a = 200; 						// 代表有价值200元的货物
int b = 100; 						// 代表现在有100元现金
 
void* ThreadA(void*) 				// 模拟伙计卖货收钱
{
	while (1)
	{
		a -= 50; 					// 卖出价值50元的货物 
		b += 50;					// 收回50元钱
	}
}
 
void* ThreadB(void*) 				// 模拟老板对账
{
	while (1)
	{
		printf("%d\n", a + b); 		// 打印当前货物和现金的总和
		sleep(1);    				// 隔1秒
	}
}
 
int main()
{
	pthread_t tida, tidb;
 
	pthread_create(&tida, NULL, ThreadA, NULL); 	// 创建伙计卖货线程
	pthread_create(&tidb, NULL, ThreadB, NULL); 	// 创建老板对账线程
	pthread_join(tida, NULL); 						// 等待线程结束
	pthread_join(tidb, NULL); 						// 等待线程结束
	return 1;
}

(2)上传test.cpp到Linux,在终端下输入命令g++ -o test test.cpp -lpthread,其中pthread是线程库的名字,然后运行test,运行结果如下:

[root@localhost cpp98]# ./test
300
250
250
300
250
300
250
^C
[root@localhost cpp98]#

按Ctrl+C快捷键后程序停止。在这个例子中,线程B每隔1秒就检查一下当前货物和现金的总和是否是300,以此来判断伙计是否私吞钱款。伙计虽然在卖力地卖货和收钱,但无奈还是出现了250,真是有口难辩啊。发生这种情况的原因是伙计在卖出货物和收货款之间被老板的对账线程打断了。下面我们用互斥锁来帮伙计证明清白。

在讲述互斥锁之前,我们首先要了解一下临界资源和临界区(Critical Section)的概念。所谓临界资源,是一次仅允许一个线程使用的共享资源。对于临界资源,各线程应该互斥地对它进行访问。每个线程中访问临界资源的那段代码称为临界区,又称临界段。因为临界资源要求每个线程互斥地对它进行访问,所以每次只准许一个线程进入临界区,进入后其他进程不允许再进入,一直要等到临界区中的线程退出。我们可以用线程同步机制来互斥地进入临界区。

一般来讲,线程进入临界区需要遵循下列原则:

(1)如果有若干线程要求进入空闲的临界区,一次仅允许一个线程进入。

(2)任何时候,处于临界区内的线程不可多于1个。若已有线程进入自己的临界区,则其他所有试图进入临界区的线程必须等待。

(3)进入临界区的线程要在有限时间内退出,以便其他线程能及时进入自己的临界区。

(4)如果进程不能进入自己的临界区,则应让出CPU(阻塞),避免进程出现“忙等”现象。

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

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

相关文章

qt处理表格,Qtxlsx库文件的安装以及导入

qt想要处理excel表格的&#xff0c;这个过程中避免不了使用Qtxlsx这个库文件。这几天花了几天时间&#xff0c;终于本地调通了。记录一下。 关于Qtxlsx的使用&#xff0c;大致分为2中方法。 方法一&#xff1a;直接下载对应的xlsx文件&#xff0c;然后在.pro文件中 这种方法是…

使用Java往Geoserver发布tif图层和shp图层

1. Maven依赖 栅格文件对应Tif文件 (即: 栅格就是tif) 矢量文件对应shp文件(即: 矢量就是shp) 注: 有的依赖可能在中央仓库及一些镜像仓库找不到需要手动指定仓库, 在依赖最下方 <!-- 中文转拼音工具类 --><dependency><groupId>com.belerweb</groupId&g…

指针的学习和理解

初级 1、指针的概念 在64位操作系统中&#xff0c;不管什么类型的指针都占8个字节 int a1; int* p&a;//p就是一个整型的指针&#xff0c;保存了a的地址2、指针和变量 int* p&a;* p100; // 等价于a100p //p&a*有两种定义&#xff1a; 定义的时候&#xff08;前…

【工具类】Java优雅的将XML转为JSON格式、XML转JSON

Java优雅的将XML转为JSON格式、XML转JSON 1. 导入依赖1.1 Maven使用1.2 Gradle使用 2. 代码编写3.运行示例 1. 导入依赖 1.1 Maven使用 <dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</vers…

TCP连接过程

文章目录 TCP连接过程 附录TCP报文中关键术语字段 后面再完整出理论、出实战、出总结 TCP连接过程 三次握手&#xff08;Three-Way Handshake&#xff09;过程。 TCP抓包结果分析&#xff1a; step1&#xff1a;Client1客户端--->Server1服务器发送SYN&#xff08;同步…

【C++二分查找 前缀和】1658. 将 x 减到 0 的最小操作数

本文涉及的基础知识点 C二分查找 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode1658. 将 x 减到 0 的最小操作数 给你一个整数数组 nums 和一个整数 x 。每一次操作时&#xff0c;你应当移除数组 nums 最左边或最右边的元素&am…

MambaCSR: 使用SSM的双交错扫描压缩图像超分辨率

MambaCSR: Dual-Interleaved Scanning for Compressed Image Super-Resolution With SSMs 2408.11758 (arxiv.org) GitHub - renyulin-f/MambaCSR: The code source of MambaCSR 摘要 本文提出了MambaCSR&#xff0c;这是一个基于Mamba的简单但有效的框架&#xff0c;用于解决…

ffmpeg读取时长、读取视频格式

ffmpeg读取时长、读取视频格式 ffmpeg读取时长ffmpeg读取视频格式 ffmpeg读取时长 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接 or 视频路径 2>&1 | grep Duration ffmpeg读取视频格式 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接…

集合及数据结构第八节(下)———— 队列(Queue)、队列的模拟实现和练习

系列文章目录 集合及数据结构第八节&#xff08;下&#xff09;———— 队列(Queue)、队列的模拟实现和练习 队列(Queue)、队列的模拟实现和练习 队列的概念队列的使用队列模拟实现循环队列双端队列练习题 文章目录 系列文章目录集合及数据结构第八节&#xff08;下&#x…

Chainlit接入DifyAI知识库接口快速实现自定义用户聊天界面

前言 由于dify只提供了一个分享用的网页应用&#xff0c;网页访问地址没法自定义&#xff0c;虽然可以接入NextWeb/ChatGPT web/open webui等开源应用。但是如果我们想直接给客户应用&#xff0c;还需要客户去设置配置&#xff0c;里面还有很多我们不想展示给客户的东西怎么办…

【C语言】文件操作 (详细!!)

1、为什么使用文件 使用文件的原因&#xff1a;使用文件主要是为了在程序的执行过程中保存、读取和交换数据。文件提供了一种持久化存储数据的方式&#xff0c;使得程序在关闭后&#xff0c;数据不会丢失&#xff0c;可以被其他程序或后续的程序执行周期重新读取和处理。 1.0 什…

实验2-1-3 输出三角形

本题要求编写程序&#xff0c;输出指定的由“*”组成的三角图案。 **输入格式&#xff1a; 本题无输入**输出格式&#xff1a; 按照下列格式输出由“*”组成的三角图案。 **** *** ** *程序: #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {int i…

leetcode 49 字母异位分词

正文 基础解法 首先&#xff0c;我们创建一个字典对象&#xff0c;然后遍历整个字符串列表&#xff0c;并且使用 sorted() 函数对字符串列表进行排序&#xff0c;所有的异位分词经过排序后它们的组成和顺序会趋于一致。但是需要注意的是 sorted 对字符串进行排序后会变成一个由…

基于element-ui 日期选择器el-date-picker, 即对日期做区间限制

需求&#xff1a; 有时候需求会让我们对日期选择器做限制&#xff0c;即控制最多可跨越多少个月份&#xff0c;其中涉及到不同年份该如何计算。 HTML&#xff1a; <el-date-pickerv-model"timePeriod"type"monthrange"value-format"yyyyMM"…

Linux系统之部署俄罗斯方块网页小游戏(三)

Linux系统之部署俄罗斯方块网页小游戏(三) 一、小游戏介绍1.1 小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看…

【CANoe使用大全】——cdd导入CANoe流程详解

&#x1f64b;‍♂️【CANoe使用大全】系列&#x1f481;‍♂️点击跳转 文章目录 1.1.CDD导入1.1 CDD文件导入流程 2. CDD文件导后配置2.1.协议配置2.2.寻址方式配置2.3.0x27 解密DLL导入2.4.诊断ID配置 3.导入效果4.CDD操作台使用4.1.指令发送 5.Fault Memory5.1 0x19 045.2…

解释图像的边缘检测算法中的Canny算法

Canny 算法是图像处理领域中一种经典的边缘检测方法&#xff0c;由 John F. Canny 在 1986 年提出。Canny 算法以其高效、可靠的边缘检测效果在图像处理和计算机视觉领域广泛应用。它具有良好的噪声抑制能力、精确的边缘定位能力以及单像素宽度的边缘输出特性。 Canny 边缘检测…

TIM输出比较之PWM驱动LED呼吸灯应用案例

文章目录 前言一、应用案例演示二、电路接线图三、应用案例代码四、应用案例分析4.1 基本思路4.2 相关库函数介绍4.3 初始化PWM模块4.3.1 RCC开启时钟4.3.2 配置时基单元4.3.3 配置输出比较单元4.3.4 配置GPIO4.3.5 运行控制 4.4 PWM输出模块4.5 主程序 前言 提示&#xff1a;…

无人机培训与装配维修技术详解

一、无人机基础理论 无人机&#xff0c;即无人驾驶航空器&#xff0c;凭借其灵活性、高效性和广泛应用性&#xff0c;已成为现代科技领域的热点之一。在学习无人机培训与装配维修技术之前&#xff0c;掌握无人机的基础理论是必不可少的。这包括但不限于&#xff1a; 1. 无人机…

Alpaca 汉化版 v2.9.3 — 免费 PS 智能 AI 插件

Alpaca是一款免费的PS智能AI插件&#xff0c;包含了6大AI功能&#xff0c;包括提示词生图、图像转绘画风格、生成式填充、文本转图像、计算图像模型、提高图像分辨率。汉化版本安装简单&#xff0c;只需解压到PhotoShop安装目录\Plug-ins文件夹即可。安装启动PhotoShop - 增效工…