无法在使用线程的终端上显示多个进度条(来自我的class),为什么?
Unable to display multiple progress bars (from my class) on the terminal using threads, why?
我正在尝试实现一项功能,使用线程在屏幕上打印多个进度条。我有一个 ProgressBar
class 可以生成进度条(如果您需要更多信息,可以查看 here,互斥锁已从中删除),它有一个 update
方法,更新循环内的柱。
然后,我正在创建一个新的MultiProgressBar
class来管理多个带线程的进度条,并在终端中同时显示它们。
#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%
我在 this example 中发现一个解决方案可能是在 MultiProgressBar
class 中使用 std::atomic<bool>
变量,当它是 True
向上移动光标,否则不做任何事情,就像在这个例子中,从 link 我把(这是 MultiProgressBar
class 的方法的定义调用 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};
但是我不明白我应该在哪里以及如何将 std::atomic<bool>
变量放入我的 MultiProgressBar
class 中。抱歉,我仍在学习如何使用线程。有人可以帮助我吗?
如果不使用某种控制台图形库,则无法执行此操作,因为无法将光标向上移动以写入上一行。您将需要 curses
之类的东西,或者 SetConsoleCursorPos
之类的 Win32 控制台 API 和朋友。
问题是每个条都会擦除线条,但在擦除之前不会更新它的行,所以每次都只是擦除同一行。在你链接的post中,MultiBar
class负责擦除和写入,所以你的也必须这样做。在您的情况下,每个条从 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
}
我正在尝试实现一项功能,使用线程在屏幕上打印多个进度条。我有一个 ProgressBar
class 可以生成进度条(如果您需要更多信息,可以查看 here,互斥锁已从中删除),它有一个 update
方法,更新循环内的柱。
然后,我正在创建一个新的MultiProgressBar
class来管理多个带线程的进度条,并在终端中同时显示它们。
#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%
我在 this example 中发现一个解决方案可能是在 MultiProgressBar
class 中使用 std::atomic<bool>
变量,当它是 True
向上移动光标,否则不做任何事情,就像在这个例子中,从 link 我把(这是 MultiProgressBar
class 的方法的定义调用 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};
但是我不明白我应该在哪里以及如何将 std::atomic<bool>
变量放入我的 MultiProgressBar
class 中。抱歉,我仍在学习如何使用线程。有人可以帮助我吗?
如果不使用某种控制台图形库,则无法执行此操作,因为无法将光标向上移动以写入上一行。您将需要 curses
之类的东西,或者 SetConsoleCursorPos
之类的 Win32 控制台 API 和朋友。
问题是每个条都会擦除线条,但在擦除之前不会更新它的行,所以每次都只是擦除同一行。在你链接的post中,MultiBar
class负责擦除和写入,所以你的也必须这样做。在您的情况下,每个条从 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
}