在线程之间拆分任务总是值得的吗?

Is it always worth to split task between threads?

本文关键字:值得 线程 之间 拆分 任务      更新时间:2023-10-16

我曾经认为多线程在执行阻塞操作时是最有效的,在此期间我们可以在另一个线程上继续执行其他指令。

最近我执行了简单的测试。我创建了一个数据向量,并在线程之间平均拆分行,并将执行时间与一个线程工作线程进行比较。多线程是赢家。

这是我的代码:

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <numeric>
#include <chrono>
double g_sum = 0;
std::mutex g_mutex;
void worker(const std::vector<double>& vec)
{
const auto vectorSum = std::accumulate(vec.begin(), vec.end(), 0.0);
std::lock_guard<std::mutex> lg(g_mutex);
std::cout << "Thread-Worker adding " << vectorSum << " to final sum ("<< g_sum <<")n";
g_sum += vectorSum;
}
int main()
{
const int ROW_SIZE = 10000000;
const int threadsSize = std::thread::hardware_concurrency();
std::cout << "Task will be seprated on " << threadsSize << " threadsn";
// data vector with row for every thread
std::vector<std::vector<double>> dataVector;
double fillVal = 1.1;
for (auto i = 0; i < threadsSize; ++i, fillVal += 1.1)
{
dataVector.push_back(std::vector<double>(ROW_SIZE, fillVal));
}
std::vector<std::thread> threadContainer;
auto start = std::chrono::system_clock::now();
for (const auto& row : dataVector)
{
std::thread thread(&worker, std::ref(row));
threadContainer.push_back(std::move(thread));
}
for (auto& thread : threadContainer)
{
thread.join();
}
auto end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
std::cout << "threads time: " << elapsed_seconds.count() << "sn";
// main thread only
g_sum = 0;
start = std::chrono::system_clock::now();
for (const auto& row : dataVector)
{
const auto vectorSum = std::accumulate(row.begin(), row.end(), 0.0);
std::cout << "Main Thread adding " << vectorSum << " to final sum ("<< g_sum <<")n";
g_sum += vectorSum;
}
end = std::chrono::system_clock::now();
elapsed_seconds = end-start;
std::cout << "one-thread time: " << elapsed_seconds.count() << "sn";
}

在具有 3 个逻辑内核的 Wandbox (https://wandbox.org/permlink/qah5auBI3ZoAe7B2( 中,多线程计时结果比单线程好两倍。

我的测试正确吗?我可以假设,跳过额外的实现时间,在线程之间拆分工作任务总是更好的选择吗?

基于任务的并行性与固定数量的线程(无超额订阅(通常是性能最佳的方法。但是,任务必须具有合理的大小,以避免过多的计划开销。IIRC 根据 TBB 的经验法则,执行任务至少需要 10k 个周期。您必须注意的一个重要细节是不同任务之间的同步。由于您通常不知道任务在哪个线程上执行,因此您必须小心不要引入死锁(例如,通过在持有锁的同时生成任务(。

但是,一个问题是否可以通过多个任务有效地解决,很大程度上取决于具体问题以及它如何映射到任务。它当然适用于您的示例,但这不能一概而论为总是更好的选择

附带说明:我建议使用现有的任务调度框架(例如,tbb(,而不是滚动自己的框架。

没有简单的答案。 多线程计时结果取决于实现。它可能更快,也可能不更快。 有很多微妙的地方:

  1. 硬件线程计数。如果在硬件线程计数为 1 时创建 100 个线程,则多线程解决方案将比单线程慢。因为线程上下文切换所花费的时间会太长。
  2. 同步实现。例如,日志记录操作缓慢。在关键部分的上下文中记录是不好的,因为它很慢。如果要在另一个线程中移动日志记录,则可以加快速度:std::cout << "Thread-Worker adding " << vectorSum << " to final sum ("<< g_sum <<")n";.您可以将 vectorSum 的值放入另一个线程中,并g_sum并记录它们。这将比在锁定部分中等待输出操作更快。