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

news2024/11/17 2:35:21

在这里插入图片描述

阅读导航

  • 一、问题概述
  • 二、解决思路
  • 三、代码实现
  • 四、代码优化

一、问题概述

面试官: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.coloradmin.cn/o/1792555.html

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

相关文章

读AI未来进行式笔记04数字医疗与机器人

1. 数字医疗 1.1. 20世纪的“现代医学”得益于史无前例的科学突破&#xff0c;使得医疗的方方面面都得到改善&#xff0c;让人类预期寿命从1900年的31岁提高到2017年的72岁 1.2. 现有的医疗数据库和流程将实现数字化 1.2.1. 患者记录 1.2.…

泛微开发修炼之旅--06自定义Action接口开发示例、源码及使用场景

文章链接&#xff1a;泛微开发修炼之旅--06自定义Action接口开发示例、源码及使用场景

创新实训2024.06.02日志:SSE、流式输出以及基于MTPE技术的MT-SSE技术

1. Why SSE&#xff1f; 之所以要做SSE&#xff0c;是因为在开发、调试以及使用我们开发的软件时&#xff0c;我发现消息的响应时间会很长。之所以会这样最主要的原因是&#xff0c;MTPE这项基于CoT的技术&#xff0c;本质上是多个单一的提示工程有机地组合在一起对大模型生成…

Java中常见错误-泛型擦除及桥接方法问题及解决方案

Java中泛型擦除及桥接方法 泛型擦除无界擦除上界擦除下界擦除 桥接方法演示案例wrong1wrong2wrong3right 原理总结 泛型擦除 ​ 泛型擦除是Java泛型机制的一个特性&#xff0c;它意味着**在编译期间&#xff0c;所有的泛型信息都会被移除&#xff0c;而在运行时&#xff0c;所…

html+CSS+js部分基础运用15

1、完成输入框内容的实时反向输出。 2、银行账户余额变动自动通知项目。 设计要求&#xff1a;单击按钮后&#xff0c;余额按照输入框的数额减少&#xff0c;同时将按钮式的提示信息&#xff08;金额&#xff09;同步改变。利用侦听属性实现余额发生变化时发出提示信息&#x…

python-flask项目的服务器线上部署

在部署这部分我首先尝试了宝塔面板&#xff0c;始终连接失败 换了一种思路选择了Xshell成功连接 首先我们需要下载个免费版本的Xshell 免费的&#xff1a;家庭/学校免费 - NetSarang Website 下载完毕打开 1新建-> 输入服务器的账号密码&#xff1a; 在所有会话中点击自…

NDIS Filter开发-PNP响应和安装

NDIS filter驱动可能是最容易生成的驱动之一&#xff0c;如果你安装了VS 2015 WDK之后&#xff0c;你可以直接生成一个能运行的Filter驱动&#xff0c;它一般是ndislwf。 和大部分硬件不同&#xff0c;NDIS Filter驱动介于软件和硬件抽象层之上&#xff0c;它和硬件相关&…

工业无线wifi系统搭配高速路由,解决联网及数据传输

​面对日益复杂的工业应用场景,企业对无线网络的高速、可靠和安全提出了更高要求。星创易联SR600系列多网口4G路由器应运而生,为工业无线WiFi系统提供了一个性能卓越的高速路由方案。&#xff08;key-iot.com/iotlist/sr600-5.html&#xff09; SR600路由器集4G LTE、虚拟专用…

c++(内存分配,构造,析构)

#include <iostream>using namespace std; class Per { private:string name;int age;double *height;double *weigh; public://无参构造Per(){cout << "Per::无参构造" << endl;}//有参构造Per(string name,int age,double height,double weigh):…

C++候捷stl-视频笔记4

一个万用的hash function 哈希函数的形式&#xff0c;一种是一般函数(右边)&#xff0c;一种是成员函数(左边)&#xff0c;类的对象将成为函数对象 具体做法例子。直接把属性的所有hash值加起来&#xff0c;会在hashtable中会产生很多的碰撞&#xff0c;放在同一个bucket中的元…

Nginx的https功能

一.HTTPS功能简介 Web网站的登录页面都是使用https加密传输的&#xff0c;加密数据以保障数据的安全&#xff0c;HTTPS能够加密信息&#xff0c;以免敏感信息被第三方获取&#xff0c;所以很多银行网站或电子邮箱等等安全级别较高的服务都会采用HTTPS协议&#xff0c;HTTPS其实…

优化家庭网络,路由器无线中继配置全攻略(中兴E1600无线中继设置/如何解决没有预埋有线网络接口的问题/使用闲置路由实现WIFI扩展)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 网络优化 📒📒 操作步骤 📒💡适用场景🚨 常见问题及解决方案⚓️ 相关链接 ⚓️📖 介绍 📖 在现代家庭生活中,WiFi已经渗透到我们生活的每一个角落,成为了日常生活中不可或缺的一部分。然而,不少用户常常遇到W…

Bytebase 作为唯一数据库工具厂商,亮相亚马逊云科技中国峰会

作为云计算行业的风向标&#xff0c;亚马逊云科技中国峰会每年都吸引着全球顶尖企业和行业精英。此次峰会不仅展示了最新的 AI 技术趋势和解决方案&#xff0c;还为参展商和与会者提供了一个卓越的交流与合作平台。 Bytebase 作为全场唯一的数据库工具厂商亮相数据区&#xff0…

Windows下Qt5.14.2连接华为IoTDA平台

一、华为IoTDA简介 华为云物联网平台&#xff08;IoT 设备接入云服务&#xff09;提供海量设备的接入和管理能力&#xff0c;将物理设备联接到云&#xff0c;支撑设备数据采集上云和云端下发命令给设备进行远程控制&#xff0c;配合华为云其他产品&#xff0c;帮助您快速构筑物…

学习笔记——IP地址网络协议——VLSM-可变长子网掩码(子网划分)

四、VLSM-可变长子网掩码(子网划分) 1、为什么要子网划分 为什么要子网划分&#xff1a;有类IP地址规划的缺陷。IP地址空间只能按照默认的类别使用&#xff0c;例如一个B类地址&#xff0c;默认掩码为255.255.0.0&#xff0c;意味着这个地址空间里有2的16次方个IP&#xff0c;…

从零开始实现自己的串口调试助手(3) - 显示底部收发,优化串口打开/关闭

注意: 1. 我们要实现自发自收&#xff0c;要将tx&#xff0c;rx连起来 2.发送的 不能是中文符号&#xff0c;因为这可能导致&#xff0c;读取到的是英文符号 --> 导致接收到的size 和发送的size 大小不一致 3.注意同时定义两个槽函数的时候两个槽函数都会被调用&#xff0c;…

2024 年最新安装MAC-vue教学包括常见错误

花了一上午时间终于将 vue 的工程文件安装好了&#xff0c;本教材是傻瓜式操作&#xff0c;按着教程一步一步操作最后就可以看到页面了。 安装Node 1.在线地址&#xff1a; https://nodejs.org/en 2、点击 Download Node.js下载即可&#xff0c;下载完成后&#xff0c;傻瓜式的…

【数智化CIO展】吉家宠物CIO张志伟:深度挖掘数据价值是数字化发展趋势,才能实现企业精细化运营...

张志伟 本文由吉家宠物CIO张志伟投递并参与由数据猿联合上海大数据联盟共同推出的《2024中国数智化转型升级优秀CIO》榜单/奖项评选。丨推荐企业&#xff1a;观远数据 大数据产业创新服务媒体 ——聚焦数据 改变商业 中国“宠物经济”热潮不断攀升&#xff0c;国内宠物市场的竞…

共享使用模型以节省磁盘空间

如果同时使用了多个工具&#xff08;例如 Easy Diffusion, Stable Diffusion UI, Comfy)&#xff0c;则可以通过共享使用保存在某个目录下的模型文件来节省磁盘空间。 1. Easy Diffusion 在Easy Diffusion中可以创建一个链接文件夹&#xff0c;以便在不同的 Stable Diffusion…

宜选影票特惠电影票api接口需要哪些技术支持?宜选影票api文档

特惠电影票API接口的开发和对接需要一系列技术支持&#xff0c;以确保数据的准确性、接口的稳定性以及用户使用的便捷性。以下是所需的主要技术支持&#xff0c;以清晰的分点表示和归纳&#xff1a; 1. API开发技术 RESTful API&#xff1a;特惠电影票API接口通常采用RESTful…