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

《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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

breaksoftware

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值