C++双线程交替打印奇偶数(活泼版)

news2025/5/31 21:08:21

C++双线程交替打印奇偶数(活泼版)

今天我们要玩一个超酷的"数字接龙"游戏——用两个线程交替打印奇偶数!就像两个同学轮流报数,一个说"1",另一个马上接"2",快跟着我一起来看看这个神奇的代码魔术吧!✨

今天需要提前铺垫的知识有:

1.线程的基本用法

2.mutex锁的玩法

3.条件变量的简单玩法

对于上述三点前置知识不太清楚的童鞋可以提前看咱们的第五大点:关键知识点泡泡

1.🎮 游戏规则说明书

游戏目标:创建两个线程小伙伴:

  • 线程A专门打印奇数(1、3、5…)

  • 线程B专门打印偶数(0、2、4…)

  • 它们要像打乒乓球一样轮流工作,直到数到100!

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
using namespace std;
// 准备好我们的"游戏道具"啦!

2.🔧 游戏道具准备区

2.1🧩 道具清单

int x = 0;               // 我们的"接力棒"数字
int n = 100;             // 终点线数字
mutex mtx;               // 互斥锁(防止抢答的小喇叭)
condition_variable cv;   // 条件变量(线程的"对讲机")
  • 接力棒(x):两个线程要传递的数字

  • 小喇叭(mtx):防止两个线程同时说话(数据竞争)

  • 对讲机(cv):让线程能互相通知"该你啦!"


3.👯‍♂️ 创建两个线程小伙伴

3.1🧑‍💻 线程A(奇数打印机)

thread t1([&, n]() {
    while (x < n) {
        unique_lock<mutex> lock(mtx); // 抓住小喇叭
        
        if (x % 2 == 0) {  // 如果发现是偶数
            cv.wait(lock);  // 放下喇叭睡觉:"轮到偶数了?叫我!"
        }
        
        cout << "线程" << this_thread::get_id() << ":" << x << endl;
        x++;               // 数字+1
        cv.notify_one();   // 喊醒线程B:"该你啦!"
    }
});

👩‍💻 线程B(偶数打印机)

thread t2([&, n]() {
    while (x < n) {
        unique_lock<mutex> lock(mtx); // 抓住小喇叭
        
        if (x % 2 != 0) {  // 如果发现是奇数
            cv.wait(lock); // 放下喇叭睡觉:"轮到奇数了?叫我!"
        }
        
        cout << "线程" << this_thread::get_id() << ":" << x << endl;
        x++;               // 数字+1
        cv.notify_one();   // 喊醒线程A:"该你啦!"
    }
});

游戏开始啦!

t1.join();  // 等待线程A完成任务
t2.join();  // 等待线程B完成任务

🤔 超有趣的工作原理图解

初始状态:x=0(偶数)

  1. 线程B先工作(因为x是偶数,线程A在睡觉)
    • 打印0 → x变成1 → 叫醒线程A
  2. 线程A被唤醒:
    • 打印1 → x变成2 → 叫醒线程B
  3. 线程B被唤醒:
    • 打印2 → x变成3 → 叫醒线程A
      …(循环直到x=100)

4.🔍原理解释(为什么可以这么写)

核心问题分析

我们需要解决三个关键问题:

  1. 共享数据保护:两个线程同时操作x会引发数据竞争

  2. 执行顺序控制:必须严格交替执行(奇-偶-奇-偶…)

  3. 线程通信机制:一个线程完成后要准确通知另一个线程


4.1🔒 第一关:共享数据保护(为什么用mutex?)

问题场景

  • 线程A正在读取x的值

  • 同时线程B正在修改x的值

  • → 导致数据不一致!

解决方案:互斥锁(mutex)

unique_lock<mutex> lock(mtx);  // 进入"VIP室",独享操作权
  • 比喻:就像洗手间的门锁🚪,一个人进去后会自动锁门,其他人必须等待

  • 关键点

    • 使用unique_lock而不是lock_guard(因为后面需要手动解锁)

    • 锁的范围要刚好覆盖共享数据的操作区域


4.2第二关:线程通信(为什么用condition_variable?)

问题场景

  • 线程A(奇数线程)发现当前是偶数时,不能简单循环等待

  • 否则会一直占用CPU资源(忙等待)

解决方案:条件变量+谓词判断

if (x % 2 == 0) {  // 谓词判断
    cv.wait(lock);  // 释放锁并等待通知
}
  • 工作原理

    1. 原子地释放锁并进入等待状态

    2. 被唤醒后自动重新获取锁

  • 为什么不用sleep?

    • sleep是盲等,无法精确响应状态变化

    • condition_variable是精准的事件通知机制


4.3🔄 第三关:执行顺序控制(为什么这样设计判断逻辑?)

线程A(奇数线程)的逻辑

if (x % 2 == 0)   // 发现是偶数就等待
cv.notify_one();  // 打印完奇数后叫醒偶数线程

线程B(偶数线程)的逻辑

if (x % 2 != 0)   // 发现是奇数就等待
cv.notify_one();  // 打印完偶数后叫醒奇数线程
  • 精妙之处

    • 每个线程只处理自己该处理的状态

    • 通过数值奇偶性自然形成状态切换

    • notify相当于"传球"动作


5.💡 关键知识点泡泡

5.1🧠 互斥锁(mutex)

就像"只有一个人能说话的小喇叭"🎤,防止两个线程同时修改共享数据x

5.2🧠 条件变量(condition_variable)

线程们的"智能对讲机"📞:

  • wait():“我去睡觉啦,有消息再叫我”

  • notify_one():“醒醒!该你干活啦!”

5.3🧠 unique_lock

智能锁管家,离开作用域会自动释放锁,再也不用担心忘记解锁啦!


6.🚨 常见bug救护车

⚠️ 忘记notify

如果线程执行完不叫醒对方,另一个线程会永远睡觉(死锁)💤

⚠️ 虚假唤醒

线程可能莫名其妙自己醒了,所以判断条件要用while而不是if(虽然我们这里if也够用啦)

⚠️ 锁的粒度

锁的范围太大(比如包住整个while循环)会导致性能下降哦!


7.完整代码展示

#include<iostream>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<functional>
using namespace std;


int main()
{
	//控制两个线程 ,实现一个线程打印奇数,一个进程打印偶数
	int x = 0;
	int n = 100;
	condition_variable cv;
	mutex mtx;
	thread t1([&, n]()
		{
			while (x < n)
			{
				unique_lock<mutex> lock(mtx);

				if (x % 2 == 0) //偶数就阻塞
				{
					cv.wait(lock);
				}

				cout << this_thread::get_id() << "号进程" << ":" << x << endl;
				x++;
				cv.notify_one();//唤醒一个锁
			}
		}
	);

	thread t2([&, n]()
		{
			while (x < n)
			{
				unique_lock<mutex> lock(mtx);

				if (x % 2 != 0) //奇数就阻塞
				{
					cv.wait(lock);
				}

				cout << this_thread::get_id() << "号进程" << ":" << x << endl;
				x++;
				cv.notify_one();//唤醒一个锁
			}
		}
	);

	t1.join();
	t2.join();



	return 0;
}

8.🎯 趣味扩展挑战

学会了咱们这个玩法之后可以尝试:

  1. 试试三个线程交替打印1、2、3?

  2. 改成打印字母A、B、C…Z怎么样?

  3. 添加颜色输出,让奇数和偶数显示不同颜色!🌈


看完这篇是不是觉得多线程超有趣?就像指挥两个乖巧的同学完美配合!快去试试这个代码吧~遇到问题欢迎在评论区留言,我会像notify_one()一样第一时间唤醒回复你!😊

记得点赞⭐收藏哟~

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

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

相关文章

技术为器,服务为本:AI时代的客服价值重构

在智能化浪潮中&#xff0c;大语言模型的出现为客户服务行业注入了全新动能。然而技术创新的价值不在于技术本身&#xff0c;而在于其赋能服务的深度与广度。AI对于我们来说&#xff0c;如同发动机之于汽车&#xff0c;重要的不是引擎参数&#xff0c;而是整车带给用户的驾驶体…

EasyVoice:开源的文本转语音工具,让文字“开口说话“

名人说&#xff1a;博观而约取&#xff0c;厚积而薄发。——苏轼《稼说送张琥》 创作者&#xff1a;Code_流苏(CSDN)&#xff08;一个喜欢古诗词和编程的Coder&#x1f60a;&#xff09; 目录 一、EasyVoice是什么&#xff1f;1. 核心特性一览2. 技术架构概览 二、安装部署指南…

扫地机产品异物进入吸尘口堵塞异常检测方案

扫地机产品异物进入吸尘口堵塞异常的检测方案 文章目录 扫地机产品异物进入吸尘口堵塞异常的检测方案一.背景二.石头的音频异常检测的方案2.1 音频检测触发点2.1.1时间周期2.1.2根据清洁机器人清扫模式或清扫区域污渍类型,即当清扫模式为深度清洁模式 或清扫区域污渍类型为重度…

C++并集查找

前言 C图论 C算法与数据结构 本博文代码打包下载 基本概念 并查集&#xff08;Union-Find&#xff09;是一种用于处理动态连通性&#xff08;直接或间接相连&#xff09;的数据结构&#xff0c;主要支持两种操作&#xff1a;union 和 find。通过这两个基本操作&#xff0c;可…

git reset --hard HEAD~1与git reset --hard origin/xxx

git reset --hard HEAD~1与git reset --hard origin/xxx git reset --hard origin/xxx有时候会太长&#xff0c;手工输入略微繁琐&#xff0c;可以考虑&#xff1a; git reset --hard HEAD~1 替代。 或者使用这种方式 git reset撤销当前分支所有修改&#xff0c;恢复到最近一…

C++ RB_Tree

一、红黑树是什么&#xff1f;—— 带颜色标记的平衡二叉搜索树 红黑树是一种自平衡二叉搜索树&#xff0c;它在每个节点上增加了一个颜色属性&#xff08;红色或黑色&#xff09;&#xff0c;通过对颜色的约束来确保树的大致平衡。这种平衡策略被称为 "弱平衡"&…

kibana解析Excel文件,生成mapping es导入Excel

一、Excel转为CSV格式 在线免费网站&#xff1a;EXCEL转CSV - 免费在线将EXCEL文件转换成CSV (cdkm.com) 二、登录kibana 点击左边菜单栏找到Machine Learning&#xff0c; 进入后上面菜单选择Data Visualizer&#xff0c;然后上穿转好的csv格式的Excel 点击导入输入建立的m…

开疆智能Profinet转Profibus网关连接EC-CM-P1 PROFIBUS DP从站通讯模块配置案例

本案例是通过开疆智能Profibus转Profinet网关将正弦研发的Profibus从站模块连接的EM600变频器接入到西门子1200PLC的配置案例。 配置过程 1. 打开网关配置软件“”新建项目并添加模块PN2DPM并设置参数 2. 设置网关的Profibus参数。如站地址&#xff0c;波特率等。&#xff08;…

Oracle RMAN自动恢复测试脚本

说明 此恢复测试脚本&#xff0c;基于rman备份脚本文章使用的fullbak.sh做的备份。 数据库将被恢复到RESTORE_LO参数设置的位置。 在恢复完成后&#xff0c;执行一个测试sql,确认数据库恢复完成&#xff0c;数据库备份是好的。恢复测试数据库的参数&#xff0c;比如SGA大小都…

零基础设计模式——结构型模式 - 代理模式

第三部分&#xff1a;结构型模式 - 代理模式 (Proxy Pattern) 在学习了享元模式如何通过共享对象来优化资源使用后&#xff0c;我们来探讨结构型模式的最后一个模式——代理模式。代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。 核心思想&#xff1a;为其…

架构意识与性能智慧的双重修炼

架构意识与性能智慧的双重修炼 ——现代软件架构师的核心能力建设指南 作者:蓝葛亮 🎯引言 在当今快速发展的技术环境中,软件架构师面临着前所未有的挑战。随着业务复杂度的不断增长和用户对性能要求的日益严苛,如何在架构设计中平衡功能实现与性能优化,已成为每个技术…

Dynamics 365 Business Central AI Sales Order Agent Copilot

#AI Copilot# #D365 BC 26 Wave# 最近很多客户都陆续升级到 Dynamics 365 Business Central 26 wave, Microsoft 提供一个基于Copilot 的Sales Order Agent&#xff0c;此文将此功能做个介绍. Explorer: 可以看到26版本上面增加了这样一个新图标。 Configuration: 配置过程…

RabbitMQ 与其他 MQ 的对比分析:Kafka/RocketMQ 选型指南(一)

一、引言 ** 在当今分布式系统大行其道的技术时代&#xff0c;消息队列作为分布式系统的关键组件&#xff0c;起着举足轻重的作用。它就像是一个可靠的信使&#xff0c;在不同的系统模块、服务之间传递信息&#xff0c;让各个部分能够高效、稳定地协同工作。消息队列能够实现系…

汽车EPS系统的核心:驱动芯片的精准控制原理

随着科技的飞速发展&#xff0c;电机及其驱动技术在现代工业、汽车电子、家用电器等领域扮演着越来越重要的角色。有刷马达因其结构简单、成本低廉、维护方便等优点&#xff0c;在市场上占据了一定的份额。然而&#xff0c;为了充分发挥有刷马达的性能&#xff0c;一款高效能、…

【Linux网络编程】传输层协议TCP,UDP

目录 一&#xff0c;UDP协议 1&#xff0c;UDP协议的格式 2&#xff0c;UDP的特点 3&#xff0c;面向数据报 4&#xff0c;UDP的缓冲区 5&#xff0c;UDP使用注意事项 6&#xff0c;基于UDP的应用层协议 二&#xff0c;对于报文的理解 三&#xff0c;TCP协议 1&…

基于Geotools的Worldpop世界人口tif解析-以中国2020年数据为例

目录 前言 一、Worldpop数据简介 1、数据来源 2、QGIS数据展示 3、元数据展示 二、GeoTools人口解析 1、Maven依赖引入 2、Tif人口计算 三、总结 前言 在当今数字化与信息化飞速发展的时代&#xff0c;地理空间数据的分析与应用已然成为诸多领域研究与决策的关键支撑。…

Unity3D仿星露谷物语开发55之保存游戏到文件

1、目标 将游戏保存到文件&#xff0c;并从文件中加载游戏。 Player在游戏中种植的Crop&#xff0c;我们希望保存到文件中&#xff0c;当游戏重新加载时Crop的GridProperty数据仍然存在。这次主要实现保存地面属性&#xff08;GridProperties&#xff09;信息。 我们要做的是…

【无标题】C++23新特性:支持打印volatile指针

文章目录 前言背景与问题C23的解决方案实现原理使用场景硬件开发多线程调试 总结 前言 在C开发中&#xff0c;volatile关键字常用于修饰变量&#xff0c;以确保编译器不会对这些变量进行优化&#xff0c;从而保证程序能够正确地与硬件交互或处理多线程环境下的特殊变量。然而&…

【第4章 图像与视频】4.2 图像的缩放

文章目录 前言示例-图像的缩放在 Canvas 边界之外绘制图像 前言 在上节中读者已经学会了如何使用 drawImage() 方法将一幅未经缩放的图像绘制到 canvas 之中。现在我们就来看看如何用该方法在绘制图像的时候进行缩放 示例-图像的缩放 未缩放的图像&#xff0c;显示图形原有大…

敏捷开发中如何避免迭代失控

在敏捷开发过程中避免迭代失控&#xff0c;需要实施合理规划迭代目标、明确职责分工、强化沟通机制、严格控制需求变更等措施&#xff0c;其中合理规划迭代目标尤为重要&#xff0c;它确保团队聚焦于关键任务&#xff0c;避免因目标不清晰而导致的迭代混乱和失控。 一、合理规划…