C++拾趣——绘制Console中多个进度条

news/2024/10/4 17:59:17 标签: c++, 开发语言

大纲

  • 多/单线程
  • 移动光标
  • 方案
    • 代码

在《C++拾趣——绘制Console中单个进度条》一文中,我们介绍了使用\r来将光标重置到一行的开头,从而实现重绘的功能。

在这里插入图片描述
但是如果我们同时有几个同步运行的进度条,该如何实现呢?

这儿要解决几个问题:

  • 是多个线程同时输出——即不同线程输出不同的进度条,还是单个线程输出。
  • \r只能将光标移动到一行的起始位置,但是多个进度条是多行。\r不能解决这个问题了,那该如何解决?

多/单线程

console是一个独占资源。如果我们使用多线程同步输出到console,需要同步机制来保证输出的正确性。

但是针对本例,即使使用同步机制,要保证输出正确也会超级复杂。

所以我们参考很多UI库例子,采用的方案是:UI线程是独立的——即只有一个UI线程。

每个进度条的变化都会保存到UI线程可以读取到的变量中。这样业务线程负责数据变化,UI线程根据新数据来绘制进度条。

移动光标

因为3个进度条占用了3行,\r只能将光标移动到一行的开始位置,这个方案已经不能解决我们的问题了。但是天无绝人之路,我们可以使用\033[【N】F来将光标移动到N行开始处。

方案

解决所有的问题后,我们着手实现该功能。

int main() {
    int duration = 5000; // 进度条持续时间(毫秒)
    const int barWidth = 50;
    auto progress = make_shared<vector<int>>(3, 0); // 初始化3个进度条的进度为0

    // 创建三个线程,每个线程更新一个进度条
    thread t1(updateProgress, progress, 0, duration);
    thread t2(updateProgress, progress, 1, duration * 2);
    thread t3(updateProgress, progress, 2, duration * 3);

    // 创建一个线程打印进度条
    thread printer(printProgress, progress, barWidth);

    // 等待所有线程完成
    t1.join();
    t2.join();
    t3.join();
    // printer.join();

    return 0;
}

我们启动3个业务线程t1、t2和t3,它们分别在5秒、10秒、15秒后结束。

这些线程会在生命周期内,将自己对应的进度从0增长到50。

它们这些进度信息保存到progress中。由于特定位置只有一个读取线程和一个写入线程操作,所以我们不需要使用任何同步机制保障数据安全。

void updateProgress(shared_ptr<vector<int>> progress, int id, int duration) {
    const int barWidth = 50;
    for (int i = 0; i <= barWidth; ++i) {
        (*progress)[id] = i;
        this_thread::sleep_for(chrono::milliseconds(duration / barWidth));
    }
}

在主线程中,我们还启动了一个UI线程。它会根据progress中的内容,不停重绘console。

有一点需要注意:我们需要保证每个进度都绘制到100%后再退出线程——而不能以progress中的数据为准。这是因为在一些情况下,可能我们绘制到99%时,progress中的3个数值都满了,这样UI线程退出,于是留在Console中的是一个残缺的状态。

我们引入bitset来记录每个进度是否都绘制到100%状态。

void printProgress(shared_ptr<vector<int>> progress, int barWidth) {
    bitset<3> finished = 0;
    
    const string red = "\033[31m";    // 红色
    const string green = "\033[32m";  // 绿色
    const string bold = "\033[1m";    // 加粗
    const string reset = "\033[0m";   // 重置颜色
    
    cout << endl << endl << endl;

    while (true) {
        cout << "\033[3F"; // 将光标移动到三行之前
        for (int id = 0; id < progress->size(); ++id) {
            cout << "Progress Bar " << id + 1 << ": [";
            for (int j = 0; j < (*progress)[id]; ++j) {
                if ((*progress)[id] == barWidth) {
                    cout << green << bold << "=" << reset;
                } else {
                    cout << red << bold << "=" << reset;
                }
            }
            for (int j = (*progress)[id]; j < barWidth; ++j) {
                cout << " ";
            }
            cout << "] ";
            if ((*progress)[id] == barWidth) {
                cout << green << bold;
                finished[id] = 1;
            } else {
                cout << red;
            }
            cout << int(((*progress)[id] / (float)barWidth) * 100.0) << "%" << reset << endl << flush;
        }
        this_thread::sleep_for(chrono::milliseconds(100)); // 每100毫秒更新一次显示

        // 检查是否所有进度条都完成
        if (finished.all()) {
            break;
        }
    }
    cout << endl;
}

代码

https://github.com/f304646673/cpulsplus/tree/master/console_ui/progress/multi


http://www.niftyadmin.cn/n/5690213.html

相关文章

HTTPS协议简单介绍

HTTP协议简单介绍HTTP协议简单介绍-CSDN博客 目录 一、对称加密和非对称加密 对称加密 非对称加密 总结 二、HTTPS协议 定义 关键特点 工作原理 详细通信过程 1. 客户端请求连接 2. 服务器响应 3. 密钥交换 4. 加密通信 5. 关闭连接 ​编辑 优势 缺点 1. 性能…

比例阀选型笔记与心得体会

比例阀选型,看这一篇就够了! 先上一张所有ITV比例阀店家都有的型号表示方法图: 我自己简化之后的型号表示方法图如下: 具体说明 第一位表示的1000/2000/3000系列肉眼可见的区别是直接影响了产品的价格,在后面的数字都一样的情况下,3000系列>2000系列>1000系列,至…

mybatis-plus使用总结

基本使用 mybatis-plus依赖 <!-- mybatis-plus开始 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.7</version></dependency><dependency>&l…

spring-boot 整合 mybatis

文章目录 Spring boot 整合Mybatis将数据返回到浏览器1. 准备数据2. 导入依赖3. 配置数据库连接4. 创建一个 pojo 包&#xff0c;创建User实体类5. 创建一个mapper包&#xff0c;写一个UserMapper接口6. 创建一个service包&#xff0c;写一个UserService接口。7. 在 Service 包…

理解C语言之深入理解指针(四)

目录 1. 回调函数是什么&#xff1f; 2. qsort使⽤举例 2.1 使⽤qsort函数排序整型数据 2.2 使⽤qsort排序结构数据 3. qsort函数的模拟实现 1. 回调函数是什么&#xff1f; 回调函数就是⼀个通过函数指针调⽤的函数。 如果你把函数的指针&#xff08;地址&#xff09;作为…

[Linux][进程] 进程终止

进程终止的三种情况 1.代码跑完&#xff0c;结果正确 main函数return 0 即可 2.代码跑完&#xff0c;结果不正确 #include <iostream>using namespace std;double Div(int a, int b) {if(b 0){// 被除数为0 ,程序将出错}return a/ b; int main() { double c Di…

基于SpringBoot+Vue+MySQL的中医院问诊系统

系统展示 用户前台界面 管理员后台界面 医生后台界面 系统背景 随着信息技术的迅猛发展和医疗服务需求的不断增加&#xff0c;传统的中医院问诊流程已经无法满足患者和医院的需求。纸质病历不仅占用大量存储空间&#xff0c;而且容易丢失和损坏&#xff0c;同时难以实现信息的快…

Python 从入门到实战33(使用MySQL)

我们的目标是&#xff1a;通过这一套资料学习下来&#xff0c;通过熟练掌握python基础&#xff0c;然后结合经典实例、实践相结合&#xff0c;使我们完全掌握python&#xff0c;并做到独立完成项目开发的能力。 上篇文章我们讨论了数据库编程接口操作的相关知识。今天我们将学习…