【面试题】创建两个线程交替打印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参数来区分是打印奇数还是偶数。

在这里插入图片描述

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

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

相关文章

Aws EC2,kubeadm方式安装kubernetes(k8s)

版本 docker版本&#xff1a;20.10.25 k8s版本&#xff08;kubeadm&#xff0c;kubelet和kubectl&#xff09;&#xff1a;1.20.10-0 初始化 # 禁用 SELinux sudo setenforce 0 sudo sed -i s/^SELINUXenforcing$/SELINUXpermissive/ /etc/selinux/config# 关闭防火墙 sudo …

MySQL的联合索引及案例分析

1. 联合索引 关于联合索引的详解参考博客【Mysql-----联合索引和最左匹配】&#xff0c;包含讲解 最左匹配 联合索引失效的情况 不遵循最左匹配原则范围查询右边失效原理like索引失效原理 比较关注的点在于&#xff1a; 对A、B、C三个字段创建一个联合索引&#xff08;A, …

在线建站流程分析

建站流程是指通过互联网创建一个个人或企业网站的过程。随着互联网的发展&#xff0c;越来越多的人和机构开始意识到网络的重要性&#xff0c;建站成为一种常见的行为。在线建站的流程一般包括以下几个步骤。 首先&#xff0c;选择一个合适的建站平台。目前&#xff0c;有很多在…

前端逆向之查看接口调用栈

一、来源 再分析前端请求接口数据的时候&#xff0c;其中有一个sid不知道是前端如何获取的&#xff0c;一般情况下只需要全局搜搜sid这个字符串或者请求接口的名称就可以了&#xff0c;基本都能找到sid的来源&#xff0c;但是今天这个不一样&#xff0c;搜什么都搜不到 接口地…

面试题------>MySQL!!!

一、连接查询 ①&#xff1a;左连接left join &#xff08;小表在左&#xff0c;大表在右&#xff09; ②&#xff1a;右连接right join&#xff08;小表在右&#xff0c;大表在左&#xff09; 二、聚合函数 SQL 中提供的聚合函数可以用来统计、求和、求最值等等 COUNT&…

AI论文工具推荐

AI 在学术界的使用情况也比较疯狂&#xff0c;特别是一些美国大学&#xff0c;用 AI 来辅助阅读文献以及辅助写论文的越来越多&#xff0c;毕竟确实可以提高写作效率&#xff0c;特别是在文献综述和初稿生成方面。 但在科研界其实&#xff0c;发现看论文的速度已经赶不上发论文…

“粘土风格”轻松拿捏,基于函数计算部署 ComfyUI实现AI生图

阿里云函数计算 FC 一键部署火爆全球工作流 AI 生图平台—— ComfyUI &#xff0c;实现更高质量的图像生成&#xff0c;三步轻松完成“黏土”创意AI画作&#xff0c;晒图赢眼部按摩器等好礼&#xff01; 活动地址&#xff1a; https://developer.aliyun.com/topic/june/fcspma…

编写备份MySQL 脚本

目录 环境准备 增量备份 增量备份和差异备份 完整代码如下 测试脚本是否正常 星期天运行脚本&#xff08;完全备份&#xff09; 星期一运备份脚本&#xff08;增量备份&#xff09; 星期二备份数据&#xff08;其他天--增量备份&#xff09; 星期三备份数据&#xff08;差异备…

【强烈推荐】四元数与三维旋转

目录 1 强烈推荐讲解四元数与三维旋转的这篇文章&#xff0c;深入浅出2 笔记2.1 复数2.1.1 复数的定义2.1.2 复数的乘法与二维旋转 2.2 三维空间中的旋转2.2.1 角轴2.2.2 旋转的分解 2.3 四元数2.3.1 四元数的定义2.3.2 四元数的乘法2.3.3 四元数与三维旋转2.3.4 三维旋转的矩阵…

数字影像产业园的三大赋能:科技、创新与无限可能

数字影像产业园作为文创产业的重要载体&#xff0c;以科技为核心驱动力&#xff0c;不断推动产业的技术革新和升级。 园区内汇聚了最前沿的数字技术资源&#xff0c;高清摄影设备、虚拟现实技术、人工智能应用等尖端科技在这里得到广泛应用&#xff0c;不仅提升了生产效率&…

能源SCI期刊,中科院4区,审稿快,IF=3.858

一、期刊名称 Frontiers in Energy Research 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;能源 影响因子&#xff1a;3.858 中科院分区&#xff1a;4区 三、期刊征稿范围 能源研究前沿出版了整个领域的严格同行评审研究&#xff0c;重点是可持续和环境…

java多线程原理

1.线程创建与启动&#xff1a;通过继承Thread类或实现Runnable接口创建线程&#xff0c;并调用start()方法启动线程。 1.线程状态&#xff1a;线程在其生命周期中有多种状态&#xff0c;包括新建、运行、阻塞、死亡等。了解这些状态以及如何在它们之间转换对于管理线程至关重要…

ICLR 2024 BACKDOOR FEDERATED LEARNING BY POISONING BACKDOOR-CRITICAL LAYERS

寻找后门攻击最有效的网络层。现有的 FL 攻击和防御方法通常关注整个模型。 他们都没有认识到后门关键&#xff08;BC&#xff09;层的存在——控制模型漏洞的一小部分层。 攻击 BC 层可达到与攻击整个模型相同的效果&#xff0c;但被最先进 (SOTA) 防御检测到的机会要小得多。…

如何卸载360安全卫士

不用像其他教程那么复杂 这篇教程比较友好 1.打开桌面&#xff0c;右键单击快捷方式 选择“打开文件位置” 2.然后&#xff0c;搜uninst.exe 3.运行 4.选择“继续卸载” 5.选择“下一步” 6.选择 “继续卸载” 7.选择“继续卸载” 8.选择“是” 9.静等卸载 10.把卸载程序关…

【因果推断python】16_工具变量2

目录 出生季度和教育对工资的影响 第一阶段 出生季度和教育对工资的影响 到目前为止&#xff0c;我们一直将这些工具视为一些神奇的变量 Z&#xff0c;它们具有仅通过干预变量影响结果的神奇特性。老实说&#xff0c;好的工具变量来之不易&#xff0c;我们不妨将它们视为奇迹…

Windows11系统 和Android 调试桥(Android Debug Bridge,ADB)工具安装,app抓取日志内容

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 Android调试桥&#xff08;ADB&#xff09;是一种多功能命令行工具&#xff0c;它允许开发者与连接到计算机上的Android设备进行通信和控制。ADB工具的作用包括但不限于&#xff1a; 安装和卸载应用程序&…

2024年先进材料与清洁能源国际会议(ICAMCE 2024)

2024 International Conference on Advanced Materials and Clean Energy 【1】大会信息 大会时间&#xff1a;2024-07-15 大会地点&#xff1a;中国三亚 截稿时间&#xff1a;2024-07-01(以官网为准&#xff09; 审稿通知&#xff1a;投稿后2-3日内通知 投稿邮箱&#xff1a…

揭秘相似矩阵:机器学习算法中的隐形“纽带”

在机器学习领域&#xff0c;数据的处理和分析至关重要。如何有效地从复杂的数据集中提取有价值的信息&#xff0c;是每一个机器学习研究者都在努力探索的问题。相似矩阵&#xff0c;作为衡量数据之间相似性的数学工具&#xff0c;在机器学习算法中扮演着不可或缺的角色。 相似矩…

Docker之路(三)docker安装nginx实现对springboot项目的负载均衡

Docker之路&#xff08;三&#xff09;dockernginxspringboot负载均衡 前言&#xff1a;一、安装docker二、安装nginx三、准备好我们的springboot项目四、将springboot项目分别build成docker镜像五、配置nginx并且启动六、nginx的负载均衡策略七、nginx的常用属性八、总结 前言…

SpringBoot高手之路02-全局异常处理器

RestControllerAdvice 可以将响应数据返回json格式然后响应 那么开始做全局异常处理器 首先先定义一个类 package com.healer.exception;import com.healer.common.Result; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.we…