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

在这里插入图片描述

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

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

相关文章

rust学习(字节数组转string)

最新在写数据传输相关的操作&#xff0c;发现string一个有趣的现象&#xff0c;代码如下&#xff1a; fn main() {let mut data:[u8;32] [0;32];data[0] a as u8;let my_str1 String::from_utf8_lossy(&data);let my_str my_str1.trim();println!("my_str len is…

用框架思维学Java:集合概览

集合这个词&#xff0c;耳熟能详&#xff0c;从小学一年级开始&#xff0c;每天早上做操时都会听到这两个字&#xff1a; 高中数学又学习到了新的集合&#xff1a; 那么Java中的集合是什么呢&#xff1f; 一&#xff0c;前言 1&#xff0c;什么是Java集合 数学集合是Java集…

模式识别涉及的常用算法

一、线性回归 1.算法执行流程&#xff1a; 算法的执行流程可以简述如下&#xff1a; 导入必要的库&#xff1a; 导入NumPy库&#xff0c;用于数值计算。导入Matplotlib库&#xff0c;用于数据可视化。导入Pandas库&#xff0c;用于数据处理&#xff08;尽管在这个例子中&#…

【Git】Git 的初识和安装

一、提出问题 不知道你工作或学习时&#xff0c;有没有遇到这样的情况&#xff1a;在编写各种文档时&#xff0c;为了防止文档丢失&#xff0c;更改失误&#xff0c;失误后能恢复到原来的版本&#xff0c;不得不复制出⼀个副本&#xff0c;比如&#xff1a; 设计文档v1设计文…

Python字符串操作详解(超详细)

Python字符串操作详解 目录 Python字符串操作详解一. 字符串创建二. 字符串拼接1. 使用 运算符2. 使用 .join() 方法 三. 字符串索引和切片1. 字符串索引2. 字符串切片3. 字符串长度和负索引4. 字符串不可变性 四. 字符串长度五. 字符串转换六. 查找子字符串七. 字符串替换八.…

xml创建模型组合体

XML创建模型组合体 创建步骤模型准备模型处理模型文件XML编写 效果 创建步骤 模型准备 CAD 提供的原始模型如下&#xff1a; 该模型存在的问题&#xff1a; 单位问题&#xff1a;CAD出图的是 mm 为单位&#xff0c;但是 mujoco 建模这边用的是以 m 为单位的&#xff1b;原点…

二刷算法训练营Day22 | 二叉树(8/9)

目录 详细布置&#xff1a; 1. 235. 二叉搜索树的最近公共祖先 2. 701. 二叉搜索树中的插入操作 3. 450. 删除二叉搜索树中的节点 详细布置&#xff1a; 1. 235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共…

onenet踩坑连接mqtt

一定注意这个version为默认 完整说明https://open.iot.10086.cn/doc/v5/fuse/detail/922 注意这里的的device是名称&#xff0c;不是id,最好产品开发那里就是都是一个名字

华安保险:核心系统分布式升级,提升保费规模处理能力2-3倍 | OceanBase企业案例

在3月20日的2024 OceanBase数据库城市行的活动中&#xff0c;安保险信息科技部总经理王在平发表了以“保险行业核心业务系统分布式架构实践”为主题的演讲。本文为该演讲的精彩回顾。 早在2019年&#xff0c;华安保险便开始与OceanBase接触&#xff0c;并着手进行数据库的升级…

spring boot3登录开发-2(3邮件验证码接口实现)

⛰️个人主页: 蒾酒 &#x1f525;系列专栏&#xff1a;《spring boot实战》 目录 写在前面 上文衔接 接口设计与实现 1.接口分析 2.实现思路 3.代码实现 1.定义验证码短信HTML模板枚举类 2.定义验证码业务接口 3. 验证码业务接口实现 4.控制层代码 4.测试 写…

三、Mapper XML的解析和注册使用

流程&#xff1a; 1.Resources加载MyBatis配置文件生成Reader字符流 2.SqlSessionFactoryBuilder开始引导构建SqlSessionFactory&#xff0c;包括两步&#xff1a; 第一步是在XMLConfigBuilder中使用dom4j解析xml文件&#xff0c;将解析的SQL包装成MappedStatement对象存入Con…

微信小程序-案例:本地生活-首页(不使用网络数据请求)

一、 1.页面效果&#xff1a; 二、 1.新建项目并添加页面 在app.json文件中&#xff1a; "pages": ["pages/home/home","pages/message/message","pages/contact/contact"] 2.配置导航栏效果 在app.json文件中&#xff1a; &quo…

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

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

Three.js中的Raycasting技术:实现3D场景交互事件的Raycaster详解

前言 在Web开发中&#xff0c;Three.js是一个极为强大的库&#xff0c;它让开发者能够轻松地在浏览器中创建和展示3D图形。随着3D技术在网页设计、游戏开发、数据可视化等领域的广泛应用&#xff0c;用户与3D场景的交互变得日益重要。而要实现这种交互&#xff0c;一个核心的技…

初识C++ · 反向迭代器简介

目录 前言 反向迭代器的实现 前言 继模拟实现了list和vector之后&#xff0c;我们对迭代器的印象也是加深了许多&#xff0c;但是我们实现的都是正向迭代器&#xff0c;还没有实现反向迭代器&#xff0c;那么为什么迟迟不实现呢&#xff1f;因为难吗&#xff1f;实际上还好。…

点击重置按钮清除el-table排序状态的高亮样式

需求&#xff1a;需要点击按钮的时候&#xff0c;清除掉el-table排序状态的高亮样式 解决方法&#xff1a;table添加ref"tableData",然后使用this.$refs.tableData.clearSort()。 <el-table:data"tableData"style"width: 100%":header-cell-s…

PHP实现抖音小程序用户登录获取openid

目录 第一步、抖音小程序前端使用tt.login获取code 第二步、前端拿到code传给后端 第三步、方法1 后端获取用户信息 第四步、方法2 抖音小程序拿到用户信息把用户信息传给后端 code2Session抖音小程序用户登录后端文档 第一步、抖音小程序前端使用tt.login获取code 前端 …

如何解决chatgpt出现503 bad gateway的问题

昨日&#xff0c;ChatGPT官网挂了&#xff0c;也就是使用web网页端访问的用户&#xff0c;会出现 bad gateway 情况。我们去ChatGPT官方的监控查看&#xff0c;已经展示相关错误。 影响的范围有&#xff1a; 影响了 ChatGPT 所有计划的所有用户。影响包括所有与 ChatGPT 相关…

Ubuntu22.04之解决:emacs无法输入中文问题(二百四十)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

js 选择一个音频文件,绘制音频的波形,从右向左逐渐前进。

选择一个音频文件&#xff0c;绘制波形&#xff0c;从右向左逐渐前进。 完整代码&#xff1a; <template><div><input type"file" change"handleFileChange" accept"audio/*" /><button click"stopPlayback" :…