在《C++拾趣——绘制Console中单个进度条》一文中,我们介绍了使用\r
来将光标重置到一行的开头,从而实现重绘的功能。
但是如果我们同时有几个同步运行的进度条,该如何实现呢?
这儿要解决几个问题:
\r
只能将光标移动到一行的起始位置,但是多个进度条是多行。\r
不能解决这个问题了,那该如何解决?console是一个独占资源。如果我们使用多线程同步输出到console,需要同步机制来保证输出的正确性。
但是针对本例,即使使用同步机制,要保证输出正确也会超级复杂。
所以我们参考很多UI库例子,采用的方案是:UI线程是独立的——即只有一个UI线程。
每个进度条的变化都会保存到UI线程可以读取到的变量中。这样业务线程负责数据变化,UI线程根据新数据来绘制进度条。
因为3个进度条占用了3行,\r
只能将光标移动到一行的开始位置,这个方案已经不能解决我们的问题了。但是天无绝人之路,我们可以使用3[【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 = "3[31m"; // 红色
const string green = "3[32m"; // 绿色
const string bold = "3[1m"; // 加粗
const string reset = "3[0m"; // 重置颜色
cout << endl << endl << endl;
while (true) {
cout << "3[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