突破编程_C++_面试(高级特性(2))

面试题8:什么是线程局部存储的技术

线程局部存储(Thread Local Storage,TLS)是一种存储变量的方法,这些变量在其所在的线程内是全局可访问的,但不能被其他线程访问,从而实现了变量的线程独立性。
在C++中,线程局部存储的技术通过 thread_local 关键字来实现。 thread_local 关键字允许声明一个变量,该变量的副本对于每个线程都是唯一的,每个线程都可以独立地访问和修改其自己的副本,而不会与其他线程的副本产生冲突。
使用 thread_local 关键字声明的变量是线程特定的,这意味着每个线程都有该变量的一个独立实例。当线程被创建时,它的 thread_local 变量会被初始化,而当线程结束时,这些变量会被销毁。
如下为样例代码:

#include <iostream>  
#include <thread>  
#include <mutex>  // 声明一个线程局部存储的整数变量  
thread_local int tlsVal = 0;std::mutex g_coutMutex;void incrementTlsVal()
{// 每个线程都会增加自己的 tlsVal 副本  tlsVal++;{std::unique_lock<std::mutex> lock(g_coutMutex);std::cout << "thread " << std::this_thread::get_id() << " tlsVal: " << tlsVal << std::endl;}
}int main()
{// 创建两个线程  std::thread t1(incrementTlsVal);std::thread t2(incrementTlsVal);// 等待线程完成  t1.join();t2.join();// 输出主线程的tls_var值,它应该仍然是0,因为它没有被修改过  std::cout << "main thread tlsVal: " << tlsVal << std::endl;return 0;
}

上面代码的输出为:

thread 9024 tlsVal: 1
thread 2632 tlsVal: 1
main thread tlsVal: 0

在上面代码中,incrementTlsVal 函数中的 tlsVal 变量是线程局部的。当 t1 和 t2 线程调用这个函数时,它们各自都会修改自己的 tlsVal 副本,而不会相互干扰。因此,每个线程都会输出其自己增加的 tlsVal 值,而主线程中的 tlsVal 值将保持为初始值0,因为它从未被主线程修改过。
注意,thread_local 变量的初始化仅在每个线程首次访问该变量时发生,而不是在线程创建时。此外, thread_local 变量的生命周期与线程的生命周期相同,当线程结束时,这些变量会被自动销毁。
thread_local 为开发者提供了一种方便的方式来处理需要在多线程环境中保持独立状态的数据。然而,过度使用 thread_local 可能会导致内存使用效率降低,因为每个线程都有它自己的变量副本。因此,在使用时应谨慎考虑是否真的需要线程局部存储。

面试题9:std::futurestd::promise是如何用于线程间通信

std::future 和 std::promise 是 C++ 标准库中的两个类,它们经常一起使用以实现线程间通信和同步。具体来说, std::promise 用于存储和设置某个值或异常,而 std::future 用于获取该值或异常。这种机制通常用于一个线程向另一个线程传递数据或信号。
以下是 std::future 和 std::promise 的基本用法:
创建 std::promise 对象:
首先,在一个线程中创建一个 std::promise 对象。这个对象将用于存储要传递给其他线程的值或异常。
获取 std::future 对象:
通过调用 std::promise 对象的 get_future() 成员函数来获取一个与之关联的 std::future 对象。这个 std::future 对象将被传递给需要接收数据的线程。
设置值或异常:
在第一个线程中,可以使用 std::promise 对象的 set_value() 函数来设置一个值,或者使用 set_exception() 函数来设置一个异常。这将使得与 std::promise 对象关联的 std::future 对象变为 ready 状态。
获取值或异常:
在第二个线程中,可以调用 std::future 对象的 get() 函数来获取存储的值或异常。如果 std::future 对象尚未 ready (即还没有被设置值或异常),则 get() 函数将阻塞,直到值或异常可用为止。
如下为样例代码:

#include <iostream>  
#include <thread>  
#include <future>  void setValueInNewThread(std::promise<int> prom) 
{// 模拟一些工作  std::this_thread::sleep_for(std::chrono::seconds(1));// 设置值  prom.set_value(20);
}int main() {// 创建一个promise对象  std::promise<int> prom;// 获取与promise关联的future对象  std::future<int> fut = prom.get_future();// 创建一个新线程,并将promise对象移动给它  std::thread t(setValueInNewThread, std::move(prom));// 在主线程中获取future对象中的值  try{int val = fut.get(); // 阻塞,直到值被设置  printf("the value is %d\n", val);}catch (...) {printf("an exception was thrown\n");}// 等待线程完成  t.join();return 0;
}

上面代码的输出为:

the value is 20

在上面代码中,创建了一个新的线程 t ,并将 std::promise 对象移动给它。这个线程在一段时间后设置了一个整数值 20 。主线程则通过 std::future 对象 fut 来获取这个值,并通过 get() 函数阻塞等待,直到值被设置。一旦值被设置,主线程将打印出这个值。
注意,std::promise和std::future通常用于一次性的值传递。如果需要多次传递值或希望在不同线程之间建立一个持续的通信通道,那么可能需要考虑其他机制,如条件变量、消息队列或管道。

面试题10:死锁产生的条件是什么,如何预防死锁的出现

死锁产生的条件主要有四个:
互斥
一个资源每次只能被一个进程使用。如果此时还有其他进程请求资源,则请求者只能等待,直到占有资源的进程使用完毕释放资源。
请求和保持
一个进程因请求资源而阻塞时,对已获得的资源保持不放。
不可剥夺
进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
循环等待
在发生死锁时,必然存在一个进程-资源的环形链。即进程集合 {P0,P1,P2,…,Pn} 中的 P0 正在等待一个 P1 占用的资源; P1 正在等待 P2 占用的资源,…,Pn 正在等待已被 P0 占用的资源。
只要系统发生死锁,这些条件必然成立。因此,预防死锁的策略主要是破坏上述四个条件中的一个或多个。
为了预防死锁,可以采取以下策略:
(1)避免循环等待:确保资源请求的顺序是一致的。如果每个线程都按照相同的顺序请求资源,那么就不可能出现循环等待的情况,从而避免了死锁。
(2)一次只请求一个资源:如果可能的话,尽量让线程一次只请求一个资源。这样可以减少资源争用的可能性,从而减少了死锁的风险。
(3)使用资源层次结构:将资源组织成层次结构,并要求线程按照层次顺序请求资源。这样,即使发生了资源争用,也不会出现循环等待。
(4)使用超时机制:在尝试获取资源时设置超时时间。如果线程在超时时间内无法获取资源,则放弃该资源并尝试其他策略。
(5)使用锁顺序协议:确保所有线程都按照相同的顺序获取锁。这可以通过为每个锁分配一个唯一的标识符,并要求线程按照标识符的顺序获取锁来实现。
为了检测死锁,可以采取以下策略:
(1)使用死锁检测算法:实现一个死锁检测算法,定期检查线程和资源的状态,以确定是否存在死锁。如果检测到死锁,可以采取适当的措施来恢复,例如通过中断一个线程来解除死锁。
(2)使用资源监视器:实现一个资源监视器,跟踪每个资源的使用情况。如果资源监视器发现某个资源被多个线程同时请求并等待,那么可能存在死锁。在这种情况下,资源监视器可以采取措施来解除死锁。
(3)使用操作系统提供的死锁检测工具:一些操作系统提供了死锁检测工具,可以帮助开发人员检测和解决死锁问题。这些工具可以监视线程和资源的状态,并在检测到死锁时发出警告或采取其他措施。
需要注意的是,死锁预防和检测策略的选择取决于具体的应用场景和需求。在选择合适的策略时,需要权衡性能、复杂性和可靠性等因素。

面试题11:std::atomic<int> 与 int 的区别是什么

std::atomic 是一个模板类,它可以用于任何整数类型(如 int, long, long long 等)以及指针类型。std::atomic<int> 提供了一种在多线程环境中对 int 类型安全执行原子操作的方式。
如下为样例代码:

#include <iostream>  
#include <thread>  
#include <atomic>  
#include <chrono>  std::atomic<int> g_atomicNum(0);		// 定义一个原子整数变量,并初始化为0  
int g_num = 0;							// 定义一个普通整数变量,并初始化为0  void incrementAtomicNum() 
{for (int i = 0; i < 10000000; i++){// 使用原子操作增加计数器的值  g_atomicNum.fetch_add(1, std::memory_order_relaxed);}
}void incrementNum()
{for (int i = 0; i < 10000000; i++){g_num++;}
}int main() 
{std::thread t1(incrementAtomicNum); // 创建第一个线程来增加原子计数器  std::thread t2(incrementAtomicNum); // 创建第二个线程来增加原子计数器  t1.join(); // 等待第一个线程完成  t2.join(); // 等待第二个线程完成  // 输出最终计数器的值,应该是20000000printf("atomic number: %d\n", g_atomicNum.load());std::thread t3(incrementNum); // 创建第三个线程来增加普通计数器  std::thread t4(incrementNum); // 创建第四个线程来增加普通计数器  t3.join(); // 等待第三个线程完成  t4.join(); // 等待第四个线程完成  // 输出最终计数器的值,结果是不确定的 printf("normal number: %d\n", g_num);return 0;
}

上面代码的输出为:

atomic number: 20000000
normal number: 13480371

普通整型变量 g_num 在两个线程中的累加之所以是一个未知量,是因为在增加的过程中,可能会发生线程间的切换。这会导致数据竞争,最终的 g_num 值可能不是预期的 20000000,而是小于这个值。
上面代码实际上是一个原子操作的样例,C++ 中的原子操作是指那些在多线程环境中执行时不可被其他线程中断的操作。这些操作要么完全执行,要么完全不执行,不会出现执行到一半被其他线程打断的情况。原子操作是并发编程中的一个重要概念,用于确保数据在多线程环境中的一致性。
在 C++ 中,原子操作通过 std::atomic 模板类来实现。 std::atomic 提供了一组成员函数,用于执行原子操作,如加载、存储、交换、比较并交换、加减等。这些操作都是针对单个原子类型的变量进行的,例如 std::atomic<int> 就是对 int 类型的变量进行原子操作。
原子操作的重要性在于它们可以防止数据竞争( data race )的发生。数据竞争是指两个或多个线程在没有同步的情况下访问同一内存位置,并且至少有一个是写入操作。如果没有原子性保证,数据竞争可能导致不可预测的结果,因为线程之间的操作可能会相互干扰。
通过使用 std::atomic ,开发者可以确保对特定变量的访问和修改是原子的,从而避免数据竞争。例如,当多个线程需要共享和修改一个计数器时,可以使用 std::atomic<int> 来确保计数器的增减操作是原子的,从而避免出现计数错误的情况。
需要注意的是,虽然 std::atomic 提供了原子操作的保证,但它并不能解决所有并发编程中的问题。例如,对于复合操作(由多个原子操作组成)的原子性,仍然需要使用其他同步机制(如互斥量、条件变量等)来确保线程安全。此外, std::atomic 也只能保证对单个原子变量的操作是原子的,对于涉及多个原子变量的复合操作,仍然需要谨慎处理。

面试题12:如何理解线程安全

线程安全( Thread Safety )是并发编程中的一个重要概念,它涉及到多个线程同时访问和修改共享数据时如何确保数据的一致性和程序的正确性。线程安全主要关注的是在并发环境下,代码的执行不会因为多个线程的交错执行而导致错误或不可预期的行为。
在C++中,线程安全通常涉及到以下几个方面:
原子操作
原子操作是指一个操作在执行过程中不会被其他线程打断的操作。在多线程环境中,如果一个操作不是原子的,那么它可能会在执行过程中被其他线程打断,导致数据的不一致。C++提供了std::atomic模板类来支持原子操作。
互斥锁( Mutexes )
互斥锁是一种同步机制,用于保护共享资源,确保同一时间只有一个线程可以访问这些资源。当线程尝试获取一个已经被其他线程持有的锁时,该线程将被阻塞,直到锁被释放。C++中可以使用std::mutex来实现互斥锁。
条件变量( Condition Variables )
条件变量通常与互斥锁一起使用,用于在多个线程之间传递信号。一个线程可以在条件变量上等待,直到另一个线程发出通知,表示某个条件已经满足。 C++ 中可以使用 std::condition_variable 来实现条件变量。
线程安全的容器和函数
C++标准库中的某些容器和函数是线程安全的,可以在多个线程之间共享和访问。例如, std::vector 和 std::list 等容器在单个线程中是安全的,但在多线程环境中,如果没有适当的同步机制,它们可能会导致数据竞争。为了在多线程环境中安全地使用这些容器,可以使用前面提到的互斥锁、条件变量等同步机制。
线程局部存储( Thread-Local Storage )
线程局部存储是一种机制,允许每个线程拥有其自己的变量副本。这样,每个线程都可以独立地修改自己的变量,而不会影响到其他线程。 C++ 中可以使用 thread_local 关键字来声明线程局部变量。
总之,线程安全是并发编程中的一个重要概念,它要求开发者在编写代码时考虑到多个线程可能同时访问和修改共享数据的情况,并采取适当的同步机制来确保数据的一致性和程序的正确性。在 C++ 中,可以通过使用原子操作、互斥锁、条件变量、线程安全的容器和函数以及线程局部存储等机制来实现线程安全。

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

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

相关文章

深入理解lambda表达式

深入理解ASP.NET Core中的中间件和Lambda表达式 var builder WebApplication.CreateBuilder(args); var app builder.Build(); app.Use(async (context, next) > { // Add code before request. await next(context);// Add code after request.}); 这段C#代码是用于设…

固定表结构与可自定义表结构

整个平台的表结构分为两种&#xff1a;固定的和可自定义的。 固定表结构适合于比较固定的信息对象&#xff0c;例如在平台的客户关系管理模块中&#xff0c;尽管各个行业有所差异&#xff0c;但是大同小异&#xff0c;可以固化表结构&#xff0c;使用实体来映射表。对象的编辑…

模型 IPO(输入、处理、输出)学习模型

系列文章 分享 模型&#xff0c;了解更多&#x1f449; 模型_总纲目录。重在提升认知。信息转化与传递。 1 模型 IPO(输入、处理、输出)学习模型的应用 1.1 项目管理知识体系 PMBOK 中的IPO应用 在项目管理领域&#xff0c;PMBOK&#xff08;Project Management Body of Know…

防火墙 iptables(二)--------------------SNAT与DNAT

一、SNAT ①SNAT 应用环境: 局域网主机共享单个公网IP地址接入Internet (私有IP不能在Internet中正常路由) ②SNAT原理: 源地址转换&#xff0c;根据指定条件修改数据包的源IP地址&#xff0c;通常被叫做源映射 数据包从内网发送到公网时&#xff0c;SNAT会把数据包的源IP由…

下一代模型:Gemini 1.5,正如它的名字一样闪亮登场

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

[uniapp的页面传参]详细讲解uniapp中页面传参的传递方式和接受方式 使用案例 代码注释

目录 一、传递方式1. URL传参2. Storage传参3. Vuex传参4.api传参eventChannel 二、接受方式1. URL传参2. Storage传参3. Vuex传参4.api传参eventChannel 三、使用案例四.提醒 在uniapp中&#xff0c;页面传参是非常常见的需求。本文将详细讲解uniapp中页面传参的传递方式和接受…

003 - Hugo, 创建文章

003 - Hugo, 创建文章创建文章单个md文件md文件图片总结 文章内容Front Matter文章目录数学公式的显示KaTeXMathJax 图片 003 - Hugo, 创建文章 创建文章 单个md文件 创建文章的方式&#xff1a; 手动创建&#xff1a;在post目录下&#xff0c;手动创建md文件。命令创建&am…

QObject 的拷贝构造和赋值操作

QObject中没有提供一个拷贝构造函数和赋值操作符给外界使用&#xff0c;其实拷贝构造和赋值的操作都是已经声明了的&#xff0c;但是它们被使用了Q_DISABLE_COPY () 宏放在了private区域。因此所有继承自QObject的类都使用这个宏声明了他们的拷贝构造函数和赋值操作符为私有。 …

Java集合篇之深入解析LinkedList

写在开头 作为ArrayList的同门师兄弟&#xff0c;LinkedList的师门地位逊色不少&#xff0c;除了在做算法题的时候我们会用到它之外&#xff0c;在实际的开发工作中我们极少使用它&#xff0c;就连它的创造者都说&#xff1a;“I wrote it&#xff0c;and I never use it”&am…

[java基础揉碎]二维数组

目录 什么是二维数组&#xff1a; 二维数组在内存中的布局: 动态初始化: 静态初始化: 杨辉三角: 使用细节和注意事项: 什么是二维数组&#xff1a; 1.从定义形式上看 int[][] 2.可以这样理解&#xff0c;原来的一维数组的每个元素是一维数组&#xff0c;就构成二维数…

OpenCV识别人脸案例实战

使用级联函数 基本流程 函数介绍 在OpenCV中&#xff0c;人脸检测使用的是cv2.CascadeClassifier.detectMultiScale()函数&#xff0c;它可以检测出图片中所有的人脸。该函数由分类器对象调用&#xff0c;其语法格式为&#xff1a; objects cv2.CascadeClassifier.detectMul…

Spring源码笔记之SpringIOC--(1)从XML文件到Bean的描述对象BeanDefinition

从XML文件到Bean的描述对象BeanDefinition 最开始学习spring的入门实践是&#xff0c;编写一个xml文件&#xff0c;然后利用spring读取xml文件中配置的bean。 编写一个xml配置文件default.xml <?xml version"1.0" encoding"UTF-8"?> <beans x…

问题:由于环境因素或人为因素干扰,致使土地生态系统的结构和功能失调,引起() #学习方法#经验分享

问题&#xff1a;由于环境因素或人为因素干扰&#xff0c;致使土地生态系统的结构和功能失调&#xff0c;引起&#xff08;) A&#xff0e;土地退化 B&#xff0e;土壤污染 C&#xff0e;生态平衡失调 D&#xff0e;土地沙化 参考答案如图所示

JavaSE-03笔记【继承~super】

文章目录 1. 继承1.1 继承概述&#xff08;理解&#xff09;1.2 如何继承&#xff08;掌握&#xff09;1.2.1 继承的语法格式1.2.2 具体举例 1.3 继承的相关特性&#xff08;掌握&#xff09;1.4 对继承自Object类的方法的测试&#xff08;理解&#xff09;1.5 难点解惑1.5.1 掌…

element ui 添加自定义方法

今天在修改 el-table 源码过程中遇到一个头大的问题&#xff0c;原本修改编译后&#xff0c;将 element的子目录lib下的文件复制到项目的响应目录里就可以了&#xff0c;但是&#xff0c;这次不知为何&#xff0c;编译老是出问题&#xff0c;实在没有办法&#xff0c;我就直接修…

JavaScript 分号踩坑

今天踩了一个JavaScript分号的坑 我想使用JavaScript的解构赋值语法来实现交换两个变量的值而不必引入第三个变量&#xff0c;代码如下所示&#xff1a; let a 100 let b 200 [a,b] [b,a] console.log(a) console.log(b)因为习惯了不加分号&#xff0c;所以没加分号产生报错…

leetcode hot 100最小花费爬楼梯

本题和之前的爬楼梯类似&#xff0c;但是需要考虑到花费的问题&#xff01;**注意&#xff0c;只有在爬的时候&#xff0c;才花费体力&#xff01;**那么&#xff0c;我们还是按照动态规划的五部曲来思考。 首先我们要确定dp数组的含义&#xff0c;那么就是我们爬到第i层所花费…

代码随想录 Leetcode134. 加油站

题目&#xff1a; 代码(首刷看解析 2024年2月15日&#xff09;&#xff1a; class Solution { public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {int curSum 0;int sum 0;int startIndex 0;for (int i 0; i < gas.size(); i)…

SCI文章复现 | GEO文章套路,数据下载和批次效应处理

原文链接&#xff1a; SCI文章复现 | GEO文章套路&#xff0c;数据下载和批次效应处理https://mp.weixin.qq.com/s/KBA67EJ7cCK5NDTUzrwJ2Q 一、前言 这是2024年春节后的第一个推送教程&#xff0c;我们也给大家赠送一个福利。将前期的付费教程免费推送给大家。其实&#xff…

【头歌·计组·自己动手画CPU】四、控制器设计(理论版) 【计算机硬件系统设计】

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎 ❤️关注 &#x1f44d;点赞 &#x1f64c;收藏 ✍️留言 文章目录 一、课程设计目的二、课程设计内容三、课程设计步骤四、课程设计总结 一、课程设计目的 掌握 CPU …