运行固定数量的线程

时间:2017-06-13 07:17:03

标签: c++ multithreading c++17

使用c++17的新标准,我想知道在一批作业完成之前是否有一个很好的方法来启动具有固定数量线程的进程。

您能告诉我如何实现此代码的所需功能:

std::vector<std::future<std::string>> futureStore;
const int batchSize             = 1000;
const int maxNumParallelThreads = 10;
int threadsTerminated           = 0;

while(threadsTerminated < batchSize)
{
    const int& threadsRunning = futureStore.size();
    while(threadsRunning < maxNumParallelThreads)
    {
        futureStore.emplace_back(std::async(someFunction));
    }
    for(std::future<std::string>& readyFuture: std::when_any(futureStore.begin(), futureStore.end()))
    {
        auto retVal = readyFuture.get(); 
        // (possibly do something with the ret val)
        threadsTerminated++;
    }
} 

我读过,曾经有一个std::when_any函数,但这个功能确实让它进入std功能。

当前标准库中是否支持此功能(不一定适用于std::future - s)?有没有办法轻松实现它,或者我必须解决类似this的问题?

2 个答案:

答案 0 :(得分:2)

在我看来,这似乎不是理想的方法:

  1. 你所有的主线程都在等待你的其他线程完成,轮询你未来的结果。几乎以某种方式浪费这个帖子......

  2. 我不知道std :: async在多长时间内以任何合适的方式重用线程的基础架构,因此每次都有可能创建全新的线程...(除此之外你可能不会创建如果您没有明确指定std::launch::async,请参阅here

  3. 我个人更喜欢另一种方法:

    1. 一次创建您想要使用的所有主题。
    2. 让每个线程运行一个循环,重复调用someFunction(),直到达到所需任务的数量。
    3. 实现可能与此示例类似:

      const int BatchSize = 20;
      int tasksStarted = 0;
      std::mutex mutex;
      std::vector<std::string> results;
      
      std::string someFunction()
      {
          puts("worker started"); fflush(stdout);
          sleep(2);
          puts("worker done"); fflush(stdout);
          return "";
      }
      
      void runner()
      {
          {
              std::lock_guard<std::mutex> lk(mutex);
              if(tasksStarted >= BatchSize)
                  return;
              ++tasksStarted;
          }
          for(;;)
          {
              std::string s = someFunction();
              {
                  std::lock_guard<std::mutex> lk(mutex);
                  results.push_back(s);
                  if(tasksStarted >= BatchSize)
                      break;
                  ++tasksStarted;
              }
          }
      }
      
      int main(int argc, char* argv[])
      {
          const int MaxNumParallelThreads = 4;
      
          std::thread threads[MaxNumParallelThreads - 1]; // main thread is one, too!
          for(int i = 0; i < MaxNumParallelThreads - 1; ++i)
          {
              threads[i] = std::thread(&runner);
          }
          runner();
      
          for(int i = 0; i < MaxNumParallelThreads - 1; ++i)
          {
              threads[i].join();
          }
      
          // use results...
      
          return 0;
      }
      

      这样,您不会重新创建每个线程,而是继续,直到完成所有任务。

      如果上述示例中的这些任务都不完全相同,您可以创建一个带有纯虚函数的基类Task(例如“execute”或“operator()”)并创建具有该实现的子类必要的(并保留任何必要的数据)。

      然后你可以将实例放入std :: vector或std :: list(好吧,我们不会迭代,列表可能在这里适当......)作为指针(否则,你得到类型擦除!)和让每个线程在完成前一个任务后删除其中一个任务(不要忘记防止竞争条件!)并执行它。只要没有剩下的任务,请返回......

答案 1 :(得分:1)

如果您不关心线程的确切数量,最简单的解决方案是:

std::vector<std::future<std::string>> futureStore(
    batchSize
);

std::generate(futureStore.begin(), futureStore.end(), [](){return std::async(someTask);});


for(auto& future : futureStore) {
    std::string value = future.get();
    doWork(value);
}

根据我的经验,std::async将在一定数量的线程spawend之后重用线程。它不会产生1000个线程。此外,在使用线程池时,您不会获得太多的性能提升(如果有的话)。我过去做了测量,整体运行时几乎相同。

我现在使用线程池的唯一原因是避免在计算循环中创建线程的延迟。如果你有时间限制,你可能会在第一次使用std :: async时错过最后期限,因为它会在第一次调用时创建线程。

这些应用程序有一个很好的线程池库。看看这里: https://github.com/vit-vit/ctpl

#include <ctpl.h>

const unsigned int numberOfThreads = 10;
const unsigned int batchSize = 1000;

ctpl::thread_pool pool(batchSize /* two threads in the pool */);
std::vector<std::future<std::string>> futureStore(
    batchSize
);

std::generate(futureStore.begin(), futureStore.end(), [](){ return pool.push(someTask);});

for(auto& future : futureStore) {
    std::string value = future.get();
    doWork(value);
}