当前位置: 首页 > news >正文

【面试题】创建两个线程交替打印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;
}

在这里插入图片描述

上面的这段代码让两个线程交替打印奇数和偶数。下面是代码实现的核心思路:

  1. 初始化同步工具

    • std::mutex mtx;:创建一个互斥锁mtx,用于保护共享资源(在这个例子中是变量xflag)的访问。
    • std::condition_variable cv;:创建一个条件变量cv,用于线程间的同步和通信。
    • bool flag = false;:创建一个标志变量flag,用于控制线程t1t2的执行顺序。
  2. 创建线程

    • 使用std::thread创建两个线程t1t2,它们将共享相同的函数对象,但执行不同的任务。
  3. 线程t1的逻辑

    • t1负责打印奇数。
    • 使用std::unique_lock锁定互斥锁mtx,确保对共享资源的安全访问。
    • 通过while (flag)循环和cv.wait(lock)调用,t1flagtrue时等待,这是为了让t2先执行。
    • flagfalse(即t2执行完毕后),t1打印当前的x值,然后将x加1。
    • flag设置为true,表示t1已经执行完毕,现在轮到t2执行。
    • 调用cv.notify_one()唤醒等待在cv上的一个线程,即t2
  4. 线程t2的逻辑

    • t2负责打印偶数。
    • 类似于t1t2首先锁定互斥锁mtx
    • 通过while(!flag)循环和cv.wait(lock)调用,t2flagfalse时等待,这是为了让t1先执行。
    • flagtrue(即t1执行完毕后),t2打印当前的x值,然后将x加1。
    • flag设置为false,表示t2已经执行完毕,现在轮到t1执行。
    • 调用cv.notify_one()唤醒等待在cv上的一个线程,即t1
  5. 等待线程结束

    • 使用t1.join()t2.join()确保主线程等待t1t2线程完成执行。
  6. 程序退出

    • return 0; 表示程序正常退出。

这种使用互斥锁、条件变量和标志变量的模式是多线程同步中常见的一种方法,它允许多个线程以一种协调的方式交替执行任务。通过这种方式,可以避免竞态条件和数据不一致的问题,确保线程安全。

四、代码优化

代码可以进行一些优化以提高其可读性和效率。

  1. 使用std::atomic
    使用std::atomic<int>代替int类型来声明x,这样可以避免在多线程环境中对x的访问需要互斥锁的保护。

  2. 减少锁的范围
    缩小互斥锁的使用范围,只在必要时锁定和解锁,以减少锁的争用。

  3. 使用std::chrono
    使用std::chrono库中的类型来指定condition_variable的超时时间,以避免长时间等待。

  4. 使用notify_all代替notify_one
    如果只有两个线程在等待同一个条件变量,使用notify_all可以避免唤醒一个线程后再次等待。

  5. 代码重构
    将线程函数提取为独立的函数,以提高代码的可读性和可维护性。

下面是优化后的代码:

#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以内数字(一个打印偶数一个打印奇数)

阅读导航 一、问题概述二、解决思路三、代码实现四、代码优化 一、问题概述 面试官&#xff1a;C多线程了解吗&#xff1f;你给我写一下&#xff0c;起两个线程交替打印0~100的奇偶数。就是有两个线程&#xff0c;一个线程打印奇数另一个打印偶数&#xff0c;它们交替输出&…...

PgMP考试结束后多久出成绩?附成绩查询方法

PgMP考试结束后多久出成绩&#xff1f;这是许多参加PgMP考试的考生都非常关心的问题。今天就给大家讲解一下PgMP考试多久可以知道成绩&#xff1f; 一、PgMP考试成绩查询时间 PgMP考试一般在考试结束后的6-8周左右才会出成绩&#xff0c;届时PMI官方会通过电子邮件的形式提醒…...

springboot项目Redis统计在线用户

springboot项目Redis统计在线用户 我的项目有个显示用户的遗忘曲线&#xff0c;需要统计在线用户以计算他们的曲线 思考了两种方案&#xff0c;但都是用Redis的bitmap数据结构Bitmap是一种特殊类型的数组&#xff0c;其中每个元素只能存储0或1。在Redis中&#xff0c;Bitmap实际…...

GNeRF论文理解

文章目录 主要解决什么问题&#xff1f;结构设计以及为什么有效果&#xff1f;个人想法。 主要解决什么问题&#xff1f; 本文主要想要解决的问题是 如何使用uncalibrated的照片来进行Nerf重建。虽然说现在已经有了一些方式可以对相机位姿进行估计和优化&#xff0c;但是他们限…...

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&#xff08;标准模板库&#xff09;中的容器是一组通用的、可复用的数据结构&#xff0c;用于存储和管理不同类型的数据。 目录 零. 简介&#xff1a; 一 . vector&#xff08;动态数组&#xff09; 二. list&#xff08;双向链表&#xff09; 三. deque&#xff08…...

AI生成沉浸式3D世界(空间照片/视频)

面向Vision Pro等空间计算设备的产品指南 & 技术实现路径 一、通俗理解 ldi3格式概览:这是一种创新的3D内容格式,专为Vision Pro、Quest等VR头戴设备设计,让你能沉浸在一个几可乱真的三维世界,体验仿佛亲临其境的感受。 内容创作:利用开源工具,结合多角度摄像捕捉,…...

【Vue】异步更新 $nextTick

文章目录 一、引出问题二、解决方案三、代码实现 一、引出问题 需求 编辑标题, 编辑框自动聚焦 点击编辑&#xff0c;显示编辑框让编辑框&#xff0c;立刻获取焦点 即下图上面结构隐藏&#xff0c;下面结构显示&#xff0c;并且显示的时候让它自动聚焦。 代码如下 问题 “…...

【uCOS-III-编程指南】

uCOS-III-编程指南 ■ [野火]uCOS-III内核实现与应用开发实战指南■■■■ ■ [野火]uCOS-III内核实现与应用开发实战指南 添加链接描述 ■ ■ ■ ■...

2004NOIP普及组真题 2. 花生采摘

线上OJ&#xff1a; 【04NOIP普及组】花生采摘 核心思想&#xff1a; 1、本题为贪心即可。 2、因为本题严格限制了顺序&#xff0c;所以先把每个节点的花生数量按降序排序。然后逐一判断下一个花生是否需要去采摘即可 3、每一次采摘完&#xff0c;记录耗时 t 以及采集的花…...

SAP-SD-21-定义用于定价补充的定价过程

图9 维护条件类型...

Android AAudio——C API创建AudioTrack(六)

虽然 AAudio 试图提供一种直接的硬件访问途径,但在某些场景下,如处理兼容性问题、使用系统服务(如 AudioFlinger)或者在某些设备上,使用 AudioTrack 可能是最有效或最合适的途径。这并不违背 AAudio 的初衷,因为它的目标是提供高性能的音频处理,而不是避免使用系统服务。…...

实验七、创建小型实验拓扑《计算机网络》

早检到底是谁发明出来的。 一、实验目的 完成本实验后&#xff0c;您将能够&#xff1a; • 设计逻辑网络。 • 配置物理实验拓扑。 • 配置 LAN 逻辑拓扑。 • 验证 LAN 连通性。 二、实验任务 在本实验中&#xff0c;将要求您连接网络设备并配置主机实现基本的网络…...

SqlServer2016企业版安装

前言 好久没有知识的累积&#xff0c;最近工作上遇到新的SqlServer2016安装&#xff0c;记录一下 参考文章 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语言的开发中我们经常需要对各种命名进行规范&#xff0c; 今天给大家介绍的是一个高效的将字符串转 驼峰命名 &#xff08;即 首字母大写的命名方式&#xff09;的函数。 // 字符串转驼峰命名 // author tekintian <tekintiangmail.com> func CamelStr(str string) …...

Python中__init__方法的魔力:构建对象的基石

Python中__init__方法的魔力&#xff1a;构建对象的基石 在Python的世界中&#xff0c;__init__方法是一个特殊的存在。它不仅是类的构造函数&#xff0c;更是对象生命周期的起点。通过__init__方法&#xff0c;我们可以初始化对象的状态&#xff0c;设置属性&#xff0c;甚至…...

Appium安装及配置(Windows环境)

在做app相关自动化测试&#xff0c;需要使用appium来做中转操作&#xff0c;下面来介绍一下appium的环境安装配置 appium官方文档&#xff1a;欢迎 - Appium Documentation 一、下载appium 下载地址&#xff1a;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进行图像处理与分类

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的在读研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三…...

C++_核心编程_多态案例二-制作饮品

#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为&#xff1a;煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例&#xff0c;提供抽象制作饮品基类&#xff0c;提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...

TDengine 快速体验(Docker 镜像方式)

简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能&#xff0c;本节首先介绍如何通过 Docker 快速体验 TDengine&#xff0c;然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker&#xff0c;请使用 安装包的方式快…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)

宇树机器人多姿态起立控制强化学习框架论文解析 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架&#xff08;一&#xff09; 论文解读&#xff1a;交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同&#xff0c;结合所安装的tensorflow的目录结构修改from语句即可。 原语句&#xff1a; from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后&#xff1a; from tensorflow.python.keras.lay…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

华为OD机试-最短木板长度-二分法(A卷,100分)

此题是一个最大化最小值的典型例题&#xff0c; 因为搜索范围是有界的&#xff0c;上界最大木板长度补充的全部木料长度&#xff0c;下界最小木板长度&#xff1b; 即left0,right10^6; 我们可以设置一个候选值x(mid)&#xff0c;将木板的长度全部都补充到x&#xff0c;如果成功…...

在 Spring Boot 中使用 JSP

jsp&#xff1f; 好多年没用了。重新整一下 还费了点时间&#xff0c;记录一下。 项目结构&#xff1a; pom: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://ww…...

Chrome 浏览器前端与客户端双向通信实战

Chrome 前端&#xff08;即页面 JS / Web UI&#xff09;与客户端&#xff08;C 后端&#xff09;的交互机制&#xff0c;是 Chromium 架构中非常核心的一环。下面我将按常见场景&#xff0c;从通道、流程、技术栈几个角度做一套完整的分析&#xff0c;特别适合你这种在分析和改…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理

在城市的某个角落&#xff0c;一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延&#xff0c;滚滚浓烟弥漫开来&#xff0c;周围群众的生命财产安全受到严重威胁。就在这千钧一发之际&#xff0c;消防救援队伍迅速行动&#xff0c;而豪越科技消防一体化安全管控平台构建的消防“…...