理解原子变量之一:从互斥锁到原子变量,最粗浅的认识

目录

三个实例对比

结论


多线程编程对于程序员来说,是一项非常重要的技能。在C++11标准问世之前,C++标准是不支持多线程的。在C++11出台前,如果你想在linux平台进行多线程编程,就要使用linux的多线程库pthread,而pthread是按照POSIX标准实现的,与C++标准无关。在C++11标准下,你可以使用std::thread,实现多线程编程。但是,多线程编程涉及的不仅仅是thread,编程者也要考虑资源竞争的情形,这就涉及到互斥锁、信号量等。这些也包含在c++11中。

当两个线程同时访问一个资源的时候,可能出现竞争的情况:假如其中至少一个线程在对此资源做写操作。这时,互斥锁的作用就表现出来了:凡是被互斥锁保护的代码片段,至多有一个线程可以访问它。关于互斥锁的使用方法,读者可自行查阅相关资料。

互斥锁的原理在我的博客《Peterson算法的分析》已经有所描述,这里不重复了。

下面看一组对比实验,共3个程序(平台:银河麒麟V4, CPU采用arm架构)。

第一个程序,在子线程里将一个int变量不断增加,然后打印结果,并显示耗时。

第二个程序,在子线程里将一个int变量不断增加,每次增加都用互斥锁保护,然后打印结果,并显示耗时。

第三个程序,在子线程里将一个atomic<int>原子变量不断增加,然后打印结果,并显示耗时。

三个实例对比

第一个程序名为nomutx.cpp

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <sys/time.h>

std::mutex mtx;
int x = 0, y = 0;
std::atomic<int> z(0);

void f1(void)
{
	struct timeval t1, t2;
	gettimeofday(&t1, NULL);
	for(int k = 0; k < 1000000; k++)
    	x++;
	gettimeofday(&t2, NULL);
 	double dT = (t2.tv_sec - t1.tv_sec) + (double)(t2.tv_usec - t1.tv_usec)/1000000.0;
	std::cout<<"t1 spent: "<<dT<<"  x = "<<x<<std::endl;
}


int main(void)
{
	std::thread t1(f1);

	t1.join();

	return 0;
}

第二个程序名为mutx.cpp

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <sys/time.h>

std::mutex mtx;
int x = 0, y = 0;
std::atomic<int> z(0);


void f2(void)
{
	struct timeval t1, t2;
	gettimeofday(&t1, NULL);
	for(int k = 0; k < 1000000; k++)
    {
		mtx.lock();
		y++;
		mtx.unlock();
	}
	gettimeofday(&t2, NULL);
 	double dT = (t2.tv_sec - t1.tv_sec) + (double)(t2.tv_usec - t1.tv_usec)/1000000.0;
	std::cout<<"t2 spent: "<<dT<<"  y = "<<y<<std::endl;
}



int main(void)
{
	std::thread t2(f2);

	t2.join();


	return 0;
}

第三个程序名为atomics.cpp

#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <sys/time.h>

std::mutex mtx;
int x = 0, y = 0;
std::atomic<int> z(0);



void f3(void)
{
    struct timeval t1, t2;
	gettimeofday(&t1, NULL);
	for(int k = 0; k < 1000000; k++)
    	z++;
	gettimeofday(&t2, NULL);
 	double dT = (t2.tv_sec - t1.tv_sec) + (double)(t2.tv_usec - t1.tv_usec)/1000000.0;
	std::cout<<"t3 spent: "<<dT<<"  z = "<<z.load()<<std::endl;
}


int main(void)
{
	std::thread t3(f3);

	t3.join();

	return 0;
}

编译,并运行,看结果:

重复三组实验,虽然最后的x,y,z取值都一样(1000000),但是耗时区别明显:普通的int变量,在不使用互斥锁保护的情况下,耗时最短,大约3毫秒左右;原子变量耗时次之,大约18毫秒;耗时最多是使用互斥锁保护的情况,50毫秒左右。

这里有人会说,上面为什么要用互斥锁和原子变量?上面三个例子并不存在资源竞争,没有必要使用。当然没必要使用了,我们这里只是对比它们的效率。

结论

不难得出如下结论:

1 使用互斥锁是有开销的

2 使用原子变量也有开销,但是其效率还是明显高于互斥锁。

古希腊哲学家德谟克利特提出了原子的概念,认为物质的基本单元是原子,原子不能再被分割。这里我们不去探讨他的对错。之所以叫原子变量,正是因为这种变量一个线程被读写时,另一个线程插不进来,很像德谟克利特口中的不可分割的原子。不同的编译器上,实现原子变量的方式各不相同,这里不做深入探讨。但是原子变量的作用,在上面的例子里面已经展示的很清楚了:它不能被两个线程同时操作,仿佛是被互斥锁保护了一样。但是其效率却高于使用互斥锁的情形。

打个比方:有一个苹果,一部分已经烂了。你想吃掉没烂的部分,就要用刀把烂的部分抠掉。这就要考验你的刀工了:如果抠的部分太小,仍然留下一部分烂苹果,你吃了就会生病(资源竞争);如果抠的部分太大,固然不会影响你的健康,但是抠掉的部分有没烂的,这些没烂的就浪费了(开销过大)。

互斥锁的颗粒度较大,或者说,它的刀工比较粗,如果把烂的部分全抠掉,难免把腐烂部分周边的好苹果也捎带抠掉一些(性能损失);而原子变量的颗粒度更细,或者说,它的刀工比较细,浪费的好苹果就少一些,性能就好于互斥锁。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

eBay自养号测评养成攻略:从环境系统搭建到养号下单

eBay账号在测评中的重要性不言而喻&#xff0c;然而&#xff0c;新注册的账号往往面临被封禁或下单即封的风险。如何养成稳定的买家号&#xff0c;成为众多商家关注的焦点。以下将详细讲解eBay测评中如何稳定养成买家号。 一、账号注册后的初期养护 新注册的eBay账号&#xf…

Docker(二):Docker的基本使用

1 Docker的基本使用 1.1 镜像相关操作 1、从DockerHub搜索镜像 [rootmaster ~]# docker search centos # 镜像名字 描述 星标 是否官方&#xff08;有OK表示为官方镜像&#xff09; NAME …

【hector mapping参数设置】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 hector mapping部分参数介绍调整hector_mapping中的参数ros常见问题总结 hector mapping部分参数介绍 在wiki.ros.org/hector_mapping界面找到3.1.4 Parameters章节…

使用wordcloud与jieba库制作词云图

目录 一、WordCloud库 例子&#xff1a; 结果&#xff1a; 二、Jieba库 两个基本方法 jieba.cut() jieba.cut_for_serch() 关键字提取&#xff1a; jieba.analyse包 extract_tags() 一、WordCloud库 词云图&#xff0c;以视觉效果提现关键词&#xff0c;可以过滤文本…

ios Framework版本号的问题。

自己创建的framework和普通的app的版本号设置的地方是有所有不同的。 framework 的版本号是在 TARGETS -> Build Settings -> current Project Version 这个地方设置的&#xff0c; 在创建framework的时候xcode 会自动创建一个framework.h的文件名&#xff0c;framewo…

Axure设计之多级菜单导航教程(中继器)

在数字化时代&#xff0c;优化产品设计&#xff0c;提升用户界面交互&#xff0c;是产品设计着重考虑的点。针对传统菜单导航复杂繁琐的问题&#xff0c;本设计提出了一套灵活的菜单导航方案&#xff0c;结合中继器与动态面板&#xff0c;实现一键搜索、菜单收藏、多级菜单导航…

真题总结和整理

补码的符号位在最高位 IEEE754 规格化要求 小数点前面是1,其他的认为是小数点后面为1即可 计算之前要对阶 左移和右移在寄存器中如果未说明定点数,可以通过移动小数点实现 涉及最小帧长要记得除以2 求用于外设的时钟周期数 指令两端只允许有寄存器,间接寻址要通过MA…

计组-层次化存储结构

这里主要看存储的整体结构&#xff0c;cache&#xff0c;内存 这里看存储结构是按什么样的层次来划分存储结构&#xff0c;速度由慢到快&#xff0c;容量由大到小&#xff0c;这是基于性价比的考虑&#xff0c;所以分为多级多层次&#xff0c;可以做到提高速度的同时没有增加多…

Rust整合Elasticsearch

Elasticsearch是什么 Lucene&#xff1a;Java实现的搜索引擎类库 易扩展高性能仅限Java开发不支持水平扩展 Elasticsearch&#xff1a;基于Lucene开发的分布式搜索和分析引擎 支持分布式、水平扩展提高RestfulAPI&#xff0c;可被任何语言调用 Elastic Stack是什么 ELK&a…

CoTAM——思维属性操纵链,一种利用大规模语言模型的新的高效快速学习方法

概述 近年来&#xff0c;大规模语言模型已显示出惊人的能力&#xff0c;可以从少量样本中学习。然而&#xff0c;这种能力需要昂贵的大规模模型&#xff0c;其运行成本是一大挑战。此外&#xff0c;在推理过程中&#xff0c;需要对所有测试输入的上下文&#xff08;包括演示&a…

Chromium 中chrome.topSites扩展接口定义c++

一、chrome.topSites 使用 chrome.topSites API 访问新标签页上显示的热门网站&#xff08;即最常访问的网站&#xff09;。不包括用户自定义的快捷方式。 权限 topSites 您必须声明“topSites”扩展程序清单中授予使用此 API 的权限。 {"name": "My exten…

物联网设备如何助力实现高效远程老人监护

在发达国家&#xff0c;老龄化进程加速&#xff0c;老年人常需医疗、行动辅助、安全保障及个人卫生护理&#xff0c;费用高昂。传统老人监护依赖护士或助理现场照料&#xff0c;而物联网远程监控方案能有效改进此模式。它通过运用传感器等技术&#xff0c;实现全天候低成本实时…

NPM 包开发与优化全面指南

前言 Hey, 我是 Immerse系列文章首发于【Immerse】&#xff0c;更多内容请关注该网站转载说明&#xff1a;转载请注明原文出处及版权声明&#xff01; 1. 理解 NPM 包的结构 1.1 package.json 文件&#xff1a;包的核心 package.json文件是 NPM 包的中央配置&#xff0c;定…

基于redis实现延迟队列

Redis实现延时队列 延时队列里装的主要是延时任务&#xff0c;用延时队列来维护延时任务的执行时间。 1、延时队列有哪些使用情景&#xff1f; 1、如果请求加锁没加成功 可以将这个请求扔到延时队列里&#xff0c;延后处理。 2、业务中有延时任务的需要 比如说&#xff0…

探索Python安全字符串处理的奥秘:MarkupSafe库揭秘

文章目录 探索Python安全字符串处理的奥秘&#xff1a;MarkupSafe库揭秘第一部分&#xff1a;背景介绍第二部分&#xff1a;MarkupSafe是什么&#xff1f;第三部分&#xff1a;如何安装MarkupSafe&#xff1f;第四部分&#xff1a;MarkupSafe的简单使用方法1. 使用escape函数2.…

Docker(一):Docker简介及安装

目录 1 Docker简介1.1 容器跟虚拟机的区别1、虚拟机是什么2、容器是什么3、容器和虚拟机的区别 1.2 为什么要学习容器1.3 Docker 是什么 2 Docker安装2.1 安装docker-centos71、环境初始化2、安装docker-ce3、配置docker镜像加速器 2.2 安装docker-ubuntu22.041、安装2、添加镜…

scp免密传输教程

scp免密传输教程 为了在使用 scp 命令时不需要输入密码&#xff0c;通常的做法是通过设置 SSH 公钥认证来实现。这种方法不仅避免了每次都要输入密码的麻烦&#xff0c;而且也更加安全。下面是如何设置 SSH 公钥认证的步骤&#xff1a; 1. 生成 SSH 密钥对&#xff08;如果你…

使用Postman发送POST请求的指南

作为一名软件测试工程师&#xff0c;掌握如何使用Postman发送POST请求是非常重要的技能。POST请求通常用于向服务器发送数据&#xff0c;以创建或更新资源。本文将详细介绍如何在Postman中发送POST请求&#xff0c;帮助你高效地进行接口测试。 什么是POST请求&#xff1f; PO…

<项目代码>YOLOv8 猫狗识别<目标检测>

YOLOv8是一种单阶段&#xff08;one-stage&#xff09;检测算法&#xff0c;它将目标检测问题转化为一个回归问题&#xff0c;能够在一次前向传播过程中同时完成目标的分类和定位任务。相较于两阶段检测算法&#xff08;如Faster R-CNN&#xff09;&#xff0c;YOLOv8具有更高的…

auto占位符(C++11~C++17)

文章目录 1. 定义1.1 注意事项 2. 推导规则3. 返回类型推导(C14)4. lambda表达式中使用auto类型推导5. 非类型模板形参占位符&#xff08;C17&#xff09; 1. 定义 在C11以前&#xff0c;auto关键字是用来声明自动变量的。从C11起auto被用来&#xff1a;声明变量时根据初始化表…