无法使用线程在终端上显示多个进度条(来自我的类),为什么



我正在尝试实现一个功能,使用线程在屏幕上打印多个进度条。我有一个ProgressBar类,它生成进度条(如果你需要更多关于它的信息,你可以在这里查看,互斥锁已经从中删除(,它有一个update方法,用于在循环中更新进度条。

然后,我创建了一个新的MultiProgressBar类,用线程管理多个进度条,并在终端中同时显示它们。

#ifndef MULTIPROGRESSBAR_H
#define MULTIPROGRESSBAR_h
#include <iostream>
#include <type_traits>
#include <tuple>
#include <functional>
#include <mutex>
#include <atomic>
#include <utility>
namespace osm
{
template <size_t... Is>
struct indices {};

template <size_t N, size_t... Is>
struct gen_indices: gen_indices <N - 1, N - 1, Is...> {};

template <size_t... Is>
struct gen_indices <0, Is...>: indices<Is...> {};

template <class... Indicators>
class make_MultiProgressBar 
{
public:

template <class... Inds>
make_MultiProgressBar( Inds&&... bars ): bars_{ std::forward <Inds> ( bars )... } {}

static size_t size() { return sizeof...( Indicators ); }

template <class Func, class... Args>
void for_one( size_t idx, Func&& func, Args&&... args )
{
call_one( idx, gen_indices <sizeof...( Indicators )> (), std::forward <Func> ( func ), std::forward <Args> ( args )... );
}

template <class Func, class... Args>
void for_each( Func&& func, Args&&... args ) 
{
call_all( gen_indices <sizeof...( Indicators )> (), std::forward <Func> ( func ), std::forward <Args> ( args )... );
}

private:
template <size_t... Ids, class Func, class... Args>
void call_one( size_t idx, indices <Ids...>, Func func, Args&&... args )
{
std::lock_guard<std::mutex> lock{mutex_};
[](...) {} 
(
(idx == Ids &&
( ( void ) std::forward <Func> ( func )( std::get <Ids> ( bars_ ), 
std::forward <Args> ( args )... ), false ) )...
);   
}

template <size_t... Ids, class Func, class... Args>
void call_all( indices <Ids...>, Func func, Args&&... args )
{
auto dummy = { ( func( std::get <Ids>( bars_ ), args...), 0 )... };
( void )dummy;
} 

std::tuple <Indicators&...> bars_;
std::mutex mutex_;
};

template <class... Indicators>
make_MultiProgressBar <typename std::remove_reference <Indicators>::type...>
MultiProgressBar( Indicators&&... inds ) 
{
return { std::forward <Indicators> ( inds )... };
}

template <class T> 
struct type_identity 
{
using type = T;
};

struct updater 
{ 
template <template <class> class PB, class bar_type>
auto operator()( PB <bar_type>& pb, typename type_identity <bar_type>::type v ) const
-> decltype( pb.update( bar_type{} ) ) 
{
return pb.update( v );
}
};
}
#endif

并且在主程序中应该工作为:

#include <iostream>
#include <thread>
#include <chrono>
#include <cmath>
#include <iomanip>
#include "../include/osmanip.h" //Header containing ProgressBar class and others.
using namespace osm;
using namespace std;
using namespace std::this_thread;
using namespace std::chrono;
ProgressBar<int> prog_int;
prog_int.setMin( 0 );
prog_int.setMax ( 100 );
prog_int.setStyle( "complete", "%", "#" );
prog_int.setBrackets( "[", "]" );
ProgressBar<int> prog_int_2;
prog_int_2.setMin( 5 );
prog_int_2.setMax ( 25 );
prog_int_2.setStyle( "complete", "%", "#" );
prog_int_2.setBrackets( "[", "]" );
ProgressBar<float> prog_float;
prog_float.setMin( 0.1f );
prog_float.setMax ( 12.1f );
prog_float.setStyle( "complete", "%", "#" );
prog_float.setBrackets( "[", "]" );
auto bars = MultiProgressBar( prog_int, prog_int_2, prog_float );
// Job for the first bar
auto job1 = [&bars]() {
for (int i = 0; i <= 100; i++) {
bars.for_one(0, updater{}, i);
sleep_for( milliseconds( 100 ) );
}
cout << endl;
};
// Job for the second bar
auto job2 = [&bars]() {
for (int i = 5; i <= 25; i++) {
bars.for_one(1, updater{}, i);
sleep_for(std::chrono::milliseconds(200));
}
cout << endl;
};
// Job for the third bar
auto job3 = [&bars]() {
for (float i = 0.1f; i <= 12.1f; i += 0.1f) {
bars.for_one(2, updater{}, i);
sleep_for(std::chrono::milliseconds(60));
}
cout << endl;
};
thread first_job(job1);
thread second_job(job2);
thread third_job(job3);
first_job.join();
second_job.join();
third_job.join();

问题是,当我运行代码时,进度条会重叠打印,如下所示:

0 [00:53] gianluca@ubuntu:~/osmanip (main)$ ./bin/main.exe 
[##                      ] 9.166668%

相反,我想要一些类似的东西:

0 [00:53] gianluca@ubuntu:~/osmanip (main)$ ./bin/main.exe 
[############            ] 45%
[#########               ] 39%
[##################      ] 72%

我在这个例子中发现,一个解决方案可能是在MultiProgressBar类中使用std::atomic<bool>变量,当它是True时,向上移动光标,否则就不做任何事情,就像在这个例子里一样,从我放置的链接(这是MultiProgressBar类的一个方法的定义,用来调用其他单个进度条的update方法,这里称为write_progress(:

public:
//Some code...
//...
void write_progress(std::ostream &os = std::cout) {
std::unique_lock lock{mutex_};

// Move cursor up if needed
if (started_)
for (size_t i = 0; i < count; ++i)
os << "x1b[A";

// Write each bar
for (auto &bar : bars_) {
bar.get().write_progress();
os << "n";
}
if (!started_)
started_ = true;
}
//Some code...
//...
private:
// [...]
std::mutex mutex_;
std::atomic<bool> started_{false};

但是,我不明白在MultiProgressBar类中应该在哪里以及如何放置std::atomic<bool>变量。抱歉,我还在学习如何使用线程。有人能帮我吗?

问题是每个条都会擦除行,但在执行之前不会更新其行,所以每次都只擦除同一行。在你链接的帖子中,MultiBar类负责擦除和写入,所以你的类也必须这样做。在您的情况下,每个条都会从for_one方法中擦除和写入自己,在调用条的更新之前,MultiProgressBar必须至少更新行的光标。这是实现它的伪代码:

updateOneProgressBar(barIndex){
rowDiff = lastUpdatedBarIndex - barIndex
if(rowDiff < 0) // move upwards
moveCursorUp(-rowDiff)
else // move downwards
moveCursorDown(rowDiff)
// now the cursor is in the correct row
eraseAndRewriteBar(barIndex)
// don't forget to update the control variable
lastUpdatedBarIndex = barIndex
}

如果不使用某种控制台图形库,就无法做到这一点,因为无法向上移动光标以写入前一行。您将需要curses之类的东西,或者SetConsoleCursorPos之类的Win32控制台API和朋友。

最新更新