使用多线程加速(std::async、std::thread 还是?)

Posted

技术标签:

【中文标题】使用多线程加速(std::async、std::thread 还是?)【英文标题】:Speed up using multithreading (std::async, std::thread, or?) 【发布时间】:2015-08-09 19:36:13 【问题描述】:

(g++ 4.6.3,cygwin,Windows 10)

我不确定是否有某种方法可以使用多线程机制来加速以下程序(我对此很陌生):

// ab.h
class A 
    // Member variables
    ...
    // Member functions
    A();
    ~A();
    int foo_1();
    void foo_2(std::vector<int>);
    ...


class B 
    ...
    void schedule(std::vector<A>& va);
    ...


// b.cc
...
void B::schedule(std::vector<A>& va) 
    std::vector<int> vc;
    vc.resize(va.size());
    for (... /* i from 0 to vc.size() */) 
        ...
        vc[i] = va[i].foo_1();
        ...
    
    for (... /* i from 0 to va.size() */) 
        ...
        va[i].foo_2(vc);
        ...
    
    // 5 more pairs of "for" loops like the above block
    ...


// main.cc
int main() 
    ...
    std::vector<A> va;
    // va.size() can be some large like 1000000
    ...
    B b;
    int simTime = 1000000000; // some large number of iterations
    for (int clock = 0; clock != simTime; ++clock) 
        b.schedule(va);
    
    ...
    return 0;

所以基本上,我有一堆A 类型的对象,它们随着clock 的增长而“前进”并同时相互通信。我的担忧是:

    我刚刚开始使用std::asyncstd::get() 重写我的每个for 循环对。这有效率吗?我从某处听说std::async 最适合涉及长时间处理(如 I/O)的函数,因为构造/破坏线程的开销不可忽略(?)。但是,我的 foo_1foo_2 函数并没有那么“大”。 如果构造/销毁线程的成本很高,那么最好创建一堆仅在开始时才需要的线程。但就我而言,这将是多个“对象线程”(我想这是不可能的)而不是“成员函数线程”(?)。是否可以只创建一个线程来服务一个对象,但以后只“附加”其不同的成员函数而没有构造/破坏开销?如果有,怎么做?

我的代码运行时间很长(即使经过我自己的一些优化),而有一个强大的 8 核服务器...

【问题讨论】:

只看上面,你似乎有点不必要地复制了一个向量vcva.size() 次。您的问题可能适用于线程池(手写或类似 microsofts ppl)。有关编写线程池的不完整指南,请参阅 this question。 这不是一个有效的问题。您说,有些事情是错误的,“因为执行需要很长时间”,但您不知道确切问题是什么(或者即使有问题根本我>)。你也想,“使用你不熟悉的多线程机制”可能会解决这个问题。我们应该做什么?调查你糟糕的设计并从头开始编写新的 API?来吧。分析您的代码并找到真正的问题,然后返回。这不是调试/分析服务。 使用多线程并不一定意味着提高性能。特别是如果实际上不超过一个 CPU。 @Mateusz Grzejek 是的,这不是调试/分析服务(是什么让您认为它是?)。我只是想就使用多线程加速我的程序的可能性寻求一些建议,不管我自己的错误,当然,这些错误由我自己决定。我不会听取您的建议,对其进行测试,如果可能由于我自己的错误而无法正常工作,我不会再责怪您。对于在给定代码上使用多线程的任何帮助,我将不胜感激。 【参考方案1】:

在您的 CPU 上拥有 8 个内核并且只使用一个内核似乎是一种资源浪费。所以你的问题是完全有道理的。由于您仅提供有关性能问题的少量信息,因此我只能为您提供一些一般性想法。

多线程不一定是所有性能问题的最佳解决方案

如果您创建线程,则需要同步访问共享信息以避免数据竞争。如果需要许多这样的同步,则存在线程之间发生争用的风险(即浪费时间等待其他线程准备好资源)。这很容易让您失去多线程的好处。

在您的特定情况下,两个循环都访问向量va 的相同元素。不幸的是,foo_1()foo2() 都没有声明为 const,这意味着它们可以修改向量的元素。所以你必须仔细检查这一点。

多线程提高吞吐量,而不是执行时间

如果您使用全部 8 个内核,您会注意到每个内核都会比执行相同的非线程代码慢一点。幸运的是,如果每个线程相互独立并且没有争用,整体吞吐量会更优(直白地说:如果2个线程执行80%的非线程,那么两者加起来仍然是160%的非线程) .

注意:如果您创建的线程数超出了 CPU 的处理能力,那么额外的线程将不得不等待,并且会产生额外的线程管理开销。 thread::hardware_concurrency() 提供了一个有用的指导(请记住,您的应用程序并不是唯一可能创建线程的应用程序)。

武器的选择:线程池 vs. std::async

如果您在开始时创建一个线程池,好处是您有许多线程准备触发。这可以避免在时间紧迫的情况下创建线程开销。请记住,只有在硬件支持的线程范围内(除非您有很多等待或 IO 延迟),这种好处才是真正的。

另一方面,有了std::async,您就掌握了C++ 实现。例如,MSVC 上的some experimentation 表明,微软的 async 显然确实重用了它创建的线程,以避免创建开销。这种方法减少了太多的中间线程,并且在某些情况下优于线程池。

结论:

由于性能取决于您的算法(主要)、CPU 线程、操作系统、编译器+标准库,我强烈建议您进行一些基准测试/分析以选择最佳方法。

【讨论】:

已经有简单的 C++11 线程池的实现,比如this。 @Youka 非常其实很简单。除非在简单的自制项目中,否则我不鼓励使用此代码。线程池不仅仅是一个包含一堆线程和一个方法的对象 - 它是更复杂的东西。线程间通信呢?死锁检测?任务中断?链接线程池甚至不是简单

以上是关于使用多线程加速(std::async、std::thread 还是?)的主要内容,如果未能解决你的问题,请参考以下文章

std::async的使用总结

C++并发与多线程 11_std::atomic叙谈std::launch(std::async) 深入

死锁使用 std::mutex 保护多线程中的 cout

使用 std::async 时 C++“无法推断模板参数”

为啥 std::async 使用同一个线程运行函数

指定线程的 std::async 模拟