无法在使用线程的终端上显示多个进度条(来自我的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中,MultiBarclass负责擦除和写入,所以你的也必须这样做。在您的情况下,每个条从 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
}