C++:线程池比单线程慢?

Jam*_*mes 7 c++ multithreading

首先,我确实查看了本网站上的其他主题,发现它们与我的问题无关,因为这些主要涉及使用I/O操作或线程创建开销的人.我的问题是我的线程池或工作者任务结构实现(在这种情况下)比单线程慢很多.我真的很困惑,不确定它是ThreadPool,任务本身,我如何测试它,线程的性质或我无法控制的东西.

// Sorry for the long code
#include <vector>
#include <queue>

#include <thread>
#include <mutex>
#include <future>

#include "task.hpp"

class ThreadPool
{
public:
    ThreadPool()
    {
        for (unsigned i = 0; i < std::thread::hardware_concurrency() - 1; i++)
            m_workers.emplace_back(this, i);

        m_running = true;
        for (auto&& worker : m_workers)
            worker.start();
    }
    ~ThreadPool()
    {
        m_running = false;
        m_task_signal.notify_all();
        for (auto&& worker : m_workers)
            worker.terminate();
    }

    void add_task(Task* task)
    {
        {
            std::unique_lock<std::mutex> lock(m_in_mutex);
            m_in.push(task);
        }
        m_task_signal.notify_one();
    }
private:
    class Worker
    {
    public:
        Worker(ThreadPool* parent, unsigned id) : m_parent(parent), m_id(id)
        {}
        ~Worker()
        {
            terminate();
        }

        void start()
        {
            m_thread = new std::thread(&Worker::work, this);
        }
        void terminate()
        {
            if (m_thread)
            {
                if (m_thread->joinable())
                {
                    m_thread->join();
                    delete m_thread;
                    m_thread = nullptr;
                    m_parent = nullptr;
                }
            }
        }
    private:
        void work()
        {
            while (m_parent->m_running)
            {               
                std::unique_lock<std::mutex> lock(m_parent->m_in_mutex);
                m_parent->m_task_signal.wait(lock, [&]()
                {
                    return !m_parent->m_in.empty() || !m_parent->m_running;
                });

                if (!m_parent->m_running) break;
                Task* task = m_parent->m_in.front();
                m_parent->m_in.pop();
                // Fixed the mutex being locked while the task is executed
                lock.unlock();

                task->execute();            
            }
        }
    private:
        ThreadPool* m_parent = nullptr;
        unsigned m_id = 0;

        std::thread* m_thread = nullptr;
    };
private:
    std::vector<Worker> m_workers;

    std::mutex m_in_mutex;
    std::condition_variable m_task_signal;
    std::queue<Task*> m_in;

    bool m_running = false;
};

class TestTask : public Task
{
public:
    TestTask() {}
    TestTask(unsigned number) : m_number(number) {}

    inline void Set(unsigned number) { m_number = number; }

    void execute() override
    {
        if (m_number <= 3)
        {
            m_is_prime = m_number > 1;
            return;
        }
        else if (m_number % 2 == 0 || m_number % 3 == 0)
        {
            m_is_prime = false;
            return;
        }
        else
        {
            for (unsigned i = 5; i * i <= m_number; i += 6)
            {
                if (m_number % i == 0 || m_number % (i + 2) == 0)
                {
                    m_is_prime = false;
                    return;
                }
            }
            m_is_prime = true;
            return;
        }
    }
public:
    unsigned m_number = 0;
    bool m_is_prime = false;
};

int main()
{
    ThreadPool pool;

    unsigned num_tasks = 1000000;
    std::vector<TestTask> tasks(num_tasks);
    for (auto&& task : tasks)
        task.Set(randint(0, 1000000000));

    auto s = std::chrono::high_resolution_clock::now();
    #if MT
    for (auto&& task : tasks)
        pool.add_task(&task);
    #else
    for (auto&& task : tasks)
        task.execute();
    #endif
    auto e = std::chrono::high_resolution_clock::now();
    double seconds = std::chrono::duration_cast<std::chrono::nanoseconds>(e - s).count() / 1000000000.0;
}
Run Code Online (Sandbox Code Playgroud)

使用VS2013 Profiler进行基准测试:

10,000,000 tasks:
    MT:
        13 seconds of wall clock time
        93.36% is spent in msvcp120.dll
        3.45% is spent in Task::execute() // Not good here
    ST:
        0.5 seconds of wall clock time
        97.31% is spent with Task::execute()
Run Code Online (Sandbox Code Playgroud)

ixS*_*Sci 6

在这些答案中通常的免责声明:唯一可以确定的方法是使用分析器工具进行测量.

但是如果没有它,我会尝试解释你的结果.首先,你的所有线程都有一个互斥锁.因此,一次只有一个线程可以执行某些任务.它会杀死你可能获得的所有收益.尽管你的线程,你的代码完全是串行的.所以至少要让你的任务执行来自互斥锁.您只需锁定互斥锁即可将任务从队列中取出 - 您无需在任务执行时保留它.

接下来,您的任务非常简单,单个线程就可以立即执行它们.你无法通过这些任务衡量任何收益.创建一些繁重的任务,可以产生一些更有趣的结果(一些更接近现实世界的任务,而不是这样的设计).

第三点:线程并非没有成本 - 上下文切换,互斥争用等.要获得真正的收益,如前两点所说,你需要的任务花费的时间比线程引入的开销要多,代码应该是真正并行而不是等待一些资源使它串行.

UPD:我查看了错误的代码部分.如果您创建具有足够大数量的任务,则任务非常复杂.


UPD2:我玩过您的代码并找到了一个很好的素数来展示MT代码是如何更好的.使用以下素数:1019048297.它将提供足够的计算复杂度来显示差异.

但为什么你的代码不能产生好的结果呢?很难说没有看到实现,randint()但我认为它很简单,在一半的情况下,它返回偶数和其他情况也不会产生太大的素数.因此,任务非常简单,以至于上下文切换以及特定实现和线程周围的其他事情通常比计算本身消耗更多时间.使用素数,我给你的任务别无选择,只花时间计算 - 没有简单的答案,因为数字很大,实际上是素数.这就是为什么大数字会给你你寻求的答案 - 更好的MT代码时间.