【面试题】创建两个线程交替打印100以内数字(一个打印偶数一个打印奇数)

阅读导航
- 一、问题概述
- 二、解决思路
- 三、代码实现
- 四、代码优化
一、问题概述
面试官:C++多线程了解吗?你给我写一下,起两个线程交替打印0~100的奇偶数。就是有两个线程,一个线程打印奇数另一个打印偶数,它们交替输出,类似这样。
偶线程:0
奇线程:1
偶线程:2
奇线程:3……
偶线程:98
奇线程:99
偶线程:100
面对突如其来的面试题,确实可能会让人感到手足无措。即便你已经掌握了多线程的相关知识,面试官突然提出一个问题,短时间内想要构思出一个解决方案可能还是有些困难。实际上,这类问题所涉及的知识点通常并不复杂,但如果在准备面试时没有遇到过类似的题目,想要迅速想出解决方案确实需要一定的技巧,而且面试官往往还要求面试者现场手写代码。
二、解决思路
回到题目本身,我们需要处理的是两个线程的协作问题,并且要求它们能够交替打印数字。这涉及到线程间的通信和同步。在这种情况下,我们可以想到的基本策略是使用锁来控制线程的执行顺序。拿到锁的线程可以执行打印操作,然后释放锁,让另一个线程有机会获取锁。这样,两个线程就可以轮流获得锁,实现交替打印的效果。
创建两个线程并不复杂,实现加锁机制也相对简单。关键在于如何确保这两个线程能够公平地轮流获取锁。我们知道,在加锁之后,线程之间会相互竞争以获取锁。C++标准库中的锁默认并不保证公平性(也就是说,不能保证先请求锁的线程一定会先获得锁),这就可能导致一个线程连续打印多次,而另一个线程则长时间无法打印。
为了解决这个问题,我们可以设计一种机制来确保两个线程能够轮流打印。例如,我们可以定义一个全局变量来指示哪个线程应该先打印,然后每个线程在尝试获取锁之前先检查这个全局变量,确保只有当它应该打印时才去竞争锁。这样,我们就可以避免一个线程长时间占用锁,从而实现两个线程的公平交替打印。
三、代码实现
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>int main()
{// 创建互斥锁用于同步线程std::mutex mtx;// 初始化全局变量x为1,代表要打印的第一个数字int x = 1;// 创建条件变量用于线程间同步std::condition_variable cv;// 标志变量,用于控制哪个线程应该执行bool flag = false;// 创建线程t1,负责打印奇数std::thread t1([&]() {for (size_t i = 0; i < 50; i++){// 锁定互斥锁std::unique_lock<std::mutex> lock(mtx);// 如果flag为true,则等待cv的通知while (flag)cv.wait(lock);// 打印当前线程ID和x的值std::cout << "奇线程: " << x << std::endl;// x加1,准备打印下一个数字++x;// 将flag设置为true,允许t2执行flag = true;// 通知一个等待cv的线程cv.notify_one(); }});// 创建线程t2,负责打印偶数std::thread t2([&]() {for (size_t i = 0; i < 50; i++){// 锁定互斥锁std::unique_lock<std::mutex> lock(mtx);// 如果flag为false,则等待cv的通知while(!flag)cv.wait(lock);// 打印当前线程ID和x的值std::cout << "偶线程: " << x << std::endl;// x加1,准备打印下一个数字++x;// 将flag设置为false,允许t1执行flag = false;// 通知一个等待cv的线程cv.notify_one();}});// 等待线程t1和t2完成t1.join();t2.join();// 程序正常退出return 0;
}

上面的这段代码让两个线程交替打印奇数和偶数。下面是代码实现的核心思路:
-
初始化同步工具:
std::mutex mtx;:创建一个互斥锁mtx,用于保护共享资源(在这个例子中是变量x和flag)的访问。std::condition_variable cv;:创建一个条件变量cv,用于线程间的同步和通信。bool flag = false;:创建一个标志变量flag,用于控制线程t1和t2的执行顺序。
-
创建线程:
- 使用
std::thread创建两个线程t1和t2,它们将共享相同的函数对象,但执行不同的任务。
- 使用
-
线程t1的逻辑:
t1负责打印奇数。- 使用
std::unique_lock锁定互斥锁mtx,确保对共享资源的安全访问。 - 通过
while (flag)循环和cv.wait(lock)调用,t1在flag为true时等待,这是为了让t2先执行。 - 当
flag为false(即t2执行完毕后),t1打印当前的x值,然后将x加1。 - 将
flag设置为true,表示t1已经执行完毕,现在轮到t2执行。 - 调用
cv.notify_one()唤醒等待在cv上的一个线程,即t2。
-
线程t2的逻辑:
t2负责打印偶数。- 类似于
t1,t2首先锁定互斥锁mtx。 - 通过
while(!flag)循环和cv.wait(lock)调用,t2在flag为false时等待,这是为了让t1先执行。 - 当
flag为true(即t1执行完毕后),t2打印当前的x值,然后将x加1。 - 将
flag设置为false,表示t2已经执行完毕,现在轮到t1执行。 - 调用
cv.notify_one()唤醒等待在cv上的一个线程,即t1。
-
等待线程结束:
- 使用
t1.join()和t2.join()确保主线程等待t1和t2线程完成执行。
- 使用
-
程序退出:
return 0;表示程序正常退出。
这种使用互斥锁、条件变量和标志变量的模式是多线程同步中常见的一种方法,它允许多个线程以一种协调的方式交替执行任务。通过这种方式,可以避免竞态条件和数据不一致的问题,确保线程安全。
四、代码优化
代码可以进行一些优化以提高其可读性和效率。
-
使用
std::atomic:
使用std::atomic<int>代替int类型来声明x,这样可以避免在多线程环境中对x的访问需要互斥锁的保护。 -
减少锁的范围:
缩小互斥锁的使用范围,只在必要时锁定和解锁,以减少锁的争用。 -
使用
std::chrono:
使用std::chrono库中的类型来指定condition_variable的超时时间,以避免长时间等待。 -
使用
notify_all代替notify_one:
如果只有两个线程在等待同一个条件变量,使用notify_all可以避免唤醒一个线程后再次等待。 -
代码重构:
将线程函数提取为独立的函数,以提高代码的可读性和可维护性。
下面是优化后的代码:
#include <iostream>
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#include <chrono>std::mutex mtx;
std::condition_variable cv;
std::atomic<int> x(1); // 使用原子操作来保证线程安全
bool flag = false;void print_numbers(bool is_odd) {for (size_t i = 0; i < 50; i++) {std::unique_lock<std::mutex> lock(mtx);while (flag != is_odd) {cv.wait(lock, []{ return flag != is_odd; }); // 使用lambda表达式指定唤醒条件}std::cout << std::this_thread::get_id() << ":" << x++ << std::endl;flag = !is_odd; // 切换flag的值cv.notify_all(); // 唤醒另一个线程}
}int main() {std::thread t1(print_numbers, true);std::thread t2(print_numbers, false);t1.join();t2.join();return 0;
}
在这个优化版本中:
x被声明为std::atomic<int>类型,因此不需要互斥锁来保护x的增加操作。- 条件变量的等待条件被封装在lambda表达式中,这样可以更清晰地指定唤醒条件。
- 使用
notify_all()来唤醒所有等待的线程,因为在这个场景中只有两个线程,所以notify_one()和notify_all()效果相同,但notify_all()是一个更通用的选择。 - 将打印逻辑抽象到
print_numbers函数中,并使用is_odd参数来区分是打印奇数还是偶数。

相关文章:
【面试题】创建两个线程交替打印100以内数字(一个打印偶数一个打印奇数)
阅读导航 一、问题概述二、解决思路三、代码实现四、代码优化 一、问题概述 面试官:C多线程了解吗?你给我写一下,起两个线程交替打印0~100的奇偶数。就是有两个线程,一个线程打印奇数另一个打印偶数,它们交替输出&…...
PgMP考试结束后多久出成绩?附成绩查询方法
PgMP考试结束后多久出成绩?这是许多参加PgMP考试的考生都非常关心的问题。今天就给大家讲解一下PgMP考试多久可以知道成绩? 一、PgMP考试成绩查询时间 PgMP考试一般在考试结束后的6-8周左右才会出成绩,届时PMI官方会通过电子邮件的形式提醒…...
springboot项目Redis统计在线用户
springboot项目Redis统计在线用户 我的项目有个显示用户的遗忘曲线,需要统计在线用户以计算他们的曲线 思考了两种方案,但都是用Redis的bitmap数据结构Bitmap是一种特殊类型的数组,其中每个元素只能存储0或1。在Redis中,Bitmap实际…...
GNeRF论文理解
文章目录 主要解决什么问题?结构设计以及为什么有效果?个人想法。 主要解决什么问题? 本文主要想要解决的问题是 如何使用uncalibrated的照片来进行Nerf重建。虽然说现在已经有了一些方式可以对相机位姿进行估计和优化,但是他们限…...
0531作业 链表
结果 整体代码 主要实现 /**实现* */ #include "./linklist.h"linklist* create_linklist(datatype param){linklist* node(linklist*)malloc(sizeof(linklist));if(NULLnode){puts("节点创建失败");}node->paramparam;node->pnextNULL;puts("…...
C++ STL - 容器
C STL(标准模板库)中的容器是一组通用的、可复用的数据结构,用于存储和管理不同类型的数据。 目录 零. 简介: 一 . vector(动态数组) 二. list(双向链表) 三. deque(…...
AI生成沉浸式3D世界(空间照片/视频)
面向Vision Pro等空间计算设备的产品指南 & 技术实现路径 一、通俗理解 ldi3格式概览:这是一种创新的3D内容格式,专为Vision Pro、Quest等VR头戴设备设计,让你能沉浸在一个几可乱真的三维世界,体验仿佛亲临其境的感受。 内容创作:利用开源工具,结合多角度摄像捕捉,…...
【Vue】异步更新 $nextTick
文章目录 一、引出问题二、解决方案三、代码实现 一、引出问题 需求 编辑标题, 编辑框自动聚焦 点击编辑,显示编辑框让编辑框,立刻获取焦点 即下图上面结构隐藏,下面结构显示,并且显示的时候让它自动聚焦。 代码如下 问题 “…...
【uCOS-III-编程指南】
uCOS-III-编程指南 ■ [野火]uCOS-III内核实现与应用开发实战指南■■■■ ■ [野火]uCOS-III内核实现与应用开发实战指南 添加链接描述 ■ ■ ■ ■...
2004NOIP普及组真题 2. 花生采摘
线上OJ: 【04NOIP普及组】花生采摘 核心思想: 1、本题为贪心即可。 2、因为本题严格限制了顺序,所以先把每个节点的花生数量按降序排序。然后逐一判断下一个花生是否需要去采摘即可 3、每一次采摘完,记录耗时 t 以及采集的花…...
SAP-SD-21-定义用于定价补充的定价过程
图9 维护条件类型...
Android AAudio——C API创建AudioTrack(六)
虽然 AAudio 试图提供一种直接的硬件访问途径,但在某些场景下,如处理兼容性问题、使用系统服务(如 AudioFlinger)或者在某些设备上,使用 AudioTrack 可能是最有效或最合适的途径。这并不违背 AAudio 的初衷,因为它的目标是提供高性能的音频处理,而不是避免使用系统服务。…...
实验七、创建小型实验拓扑《计算机网络》
早检到底是谁发明出来的。 一、实验目的 完成本实验后,您将能够: • 设计逻辑网络。 • 配置物理实验拓扑。 • 配置 LAN 逻辑拓扑。 • 验证 LAN 连通性。 二、实验任务 在本实验中,将要求您连接网络设备并配置主机实现基本的网络…...
SqlServer2016企业版安装
前言 好久没有知识的累积,最近工作上遇到新的SqlServer2016安装,记录一下 参考文章 SQL Server 2016软件安装包和安装教程 - 哔哩哔哩 (bilibili.com) 安装包准备 需要提前准备软件安装包如下 cn_sql_server_2016_enterprise_x64_dvd_8699450&…...
HBase数据库面试知识点:第一部分 - 基础概念与特点(持续更新中)
目录 一、HBase基础概念 1. HBase定义 2. 核心组件 3. HBase的特点 二、HBase与传统RDBMS的区别 1. 数据类型 2. 数据操作 3. 存储方式 4. 伸缩性 5. 事务性 三、HBase数据模型 四、HBase的特点 五、HBase与Hadoop生态系统的关系 一、HBase基础概念 1. HBase定义 …...
一个高效的go语言字符串转驼峰命名算法实现函数
在go语言的开发中我们经常需要对各种命名进行规范, 今天给大家介绍的是一个高效的将字符串转 驼峰命名 (即 首字母大写的命名方式)的函数。 // 字符串转驼峰命名 // author tekintian <tekintiangmail.com> func CamelStr(str string) …...
Python中__init__方法的魔力:构建对象的基石
Python中__init__方法的魔力:构建对象的基石 在Python的世界中,__init__方法是一个特殊的存在。它不仅是类的构造函数,更是对象生命周期的起点。通过__init__方法,我们可以初始化对象的状态,设置属性,甚至…...
Appium安装及配置(Windows环境)
在做app相关自动化测试,需要使用appium来做中转操作,下面来介绍一下appium的环境安装配置 appium官方文档:欢迎 - Appium Documentation 一、下载appium 下载地址:https://github.com/appium/appium-desktop/releases?page3 通…...
CANOE制造dll文件,以及应用dll文件
1、使用canoe自带的capl dll 2、然后使用Visual Studio 2022 打开项目 3、项目打开后修改下项目属性 4、修改capldll.cpp文件 4.1 添加的内容 void CAPLEXPORT far CAPLPASCAL appSum(long i, long j, long* s){*s i j;} {"sum", (CAPL_FARCALL)appSum, "…...
C++结合OpenCV进行图像处理与分类
⭐️我叫忆_恒心,一名喜欢书写博客的在读研究生👨🎓。 如果觉得本文能帮到您,麻烦点个赞👍呗! 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...
Ubuntu系统下交叉编译openssl
一、参考资料 OpenSSL&&libcurl库的交叉编译 - hesetone - 博客园 二、准备工作 1. 编译环境 宿主机:Ubuntu 20.04.6 LTSHost:ARM32位交叉编译器:arm-linux-gnueabihf-gcc-11.1.0 2. 设置交叉编译工具链 在交叉编译之前&#x…...
<6>-MySQL表的增删查改
目录 一,create(创建表) 二,retrieve(查询表) 1,select列 2,where条件 三,update(更新表) 四,delete(删除表…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
前端导出带有合并单元格的列表
// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
Springboot社区养老保险系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,社区养老保险系统小程序被用户普遍使用,为方…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
