2

我一直在考虑可以用于并行计算的工人阶级。

我想避免在创建对象后必须将工作人员显式移动到单独线程的情况,即:

Worker worker;
QThread thread;
worker.moveToThread(&thread);
thread.start();
worker.start();

这是我想出的解决方案:

标题:

#include <QObject>
#include <QThread>

class ThreadedWorker : public QObject
{
    Q_OBJECT
public:
    explicit ThreadedWorker(QObject *parent = 0);
signals:
    void finished(ThreadedWorker* worker);
public slots:
    void start();
protected:
    virtual void run();
    QThread workerThread_;
    bool isRunning_;
};

来源:

#include "threadedworker.h"

ThreadedWorker::ThreadedWorker(QObject *parent) : QObject(parent)
{
    this->moveToThread(&this->workerThread_);
    this->workerThread_.start();
    this->isRunning_ = false;
}

void ThreadedWorker::start()
{
    if(!this->isRunning_)
     {
         this->isRunning_ = true;
         this->run();
     }
 }

 void ThreadedWorker::run()
 {
     // HERE GOES THE ACTUAL WORK FOR THE WORKER
     //
     //
     emit this->finished(this); // added to inform a controller object that the worker has finished
   }

Zailborg评论后更新:

所以现在我只是创建:

ThreadedWorker worker1;
ThreadedWorker worker2;

通过一些外部信号调用它们的start()插槽,它们并行运行。

QThread workerThread_然而,我主要关心的是作为类的成员ThreadedWorker并将对象移动到构造函数中的该线程是否不是一个坏习惯。

4

3 回答 3

3

将 QThread workerThread_ 作为 ThreadedWorker 类的成员是否不是一个坏习惯

当一个对象被移动到它的线程时,这个对象和它的子对象都会被移动。当您将父对象传递给 QObject 派生类的构造函数或调用 setParent 时,子对象是 Qt 父子层次结构中链接的对象。

对于成员指针,例如 QThread指针,它不是类的“子”。因此,当 ThreadedWorker 对象被移动到新线程时,这将起作用。但是,由于线程亲和性的混淆,可能会出现问题。主对象移动到新线程,但持有一个成员指针,指向线程亲和性不同的对象;QThread* 指向的对象。

问题中提供的代码不是指 QThread 指针,而是 QThread 实例。考虑到这一点,请考虑QObject::moveToThread的文档,它指出:-

警告:这个函数不是线程安全的;当前线程必须与当前线程关联性相同。换句话说,这个函数只能将一个对象从当前线程“推”到另一个线程,它不能从任意线程“拉”一个对象到当前线程。

所以,我怀疑 QThread 和 QObject::moveToThread 的设计期望 QThread 的亲和性是稳定的,不会被改变。作为被移动对象的成员,情况并非如此。

出于这个原因,我会说将 QThread 实例作为 QObject 的成员并将对象移动到该线程并不是一个好主意。

如果您打算使用 QThread,那么我建议您阅读并遵循如何真正、真正使用 QThread 中概述的方法。

另外,经常被忽略的是 moveToThread 的功能允许 QThread 与 QObject 是一对多的关系,因此创建一个 QThread 对象并将多个 QObject 实例移动到新线程是完全可以接受的。此外,创建比可用处理器内核更多的线程通常没有什么好处。

于 2014-11-18T14:25:37.913 回答
0

看看:什么是实现 QThread 的正确方法......(请举例......)

除了我的评论之外,我认为在堆栈上分配一个对象并将其移动到不同的线程是不明智的。

于 2014-11-18T14:07:01.240 回答
0

对于任何感兴趣的人,一个小助手类允许创建具有自动可变参数推导的工人。请注意,C++17 是必需的(即不需要模板参数的规范)。完整的源代码见:https ://github.com/Broekman/Qt5_template

工人.hpp

#ifndef QT5_UI_WORKER_HPP
#define QT5_UI_WORKER_HPP

#include <QObject>
#include <QString>
#include <functional>
#include <tuple>

namespace ui
{
    class worker_object :
            public QObject
    {
    Q_OBJECT

    public:
        inline worker_object() = default;
        inline ~worker_object() = default;

    public slots:
        inline virtual void run() { /*...*/ };

    signals:
        void finished();
        void error(const QString& err_msg);
    };

    namespace helper
    {
        template <int... Is>
        struct index {};

        template <int N, int... Is>
        struct gen_seq : gen_seq<N - 1, N - 1, Is...> {};

        template <int... Is>
        struct gen_seq<0, Is...> : index<Is...> {};
    }

    template<typename... Ts>
    class worker :
            public worker_object
    {
    public: /* Functions */
        template<typename Func, typename... Args>
        inline worker(Func&& fn, Args&& ... args) :
                fn_(std::forward<Func>(fn)),
                args_(std::forward<Args>(args)...)
        { /*...*/ }

        inline ~worker() = default;

        inline void run() override
        {
            func(args_);
            emit finished();
        }

    private: /* Functions */
        template<typename... Args, int... Is>
        inline void func(std::tuple<Args...>& tup, helper::index<Is...>)
        {
            fn_(std::get<Is>(tup)...);
        }

        template<typename... Args>
        inline void func(std::tuple<Args...>& tup)
        {
            func(tup, helper::gen_seq<static_cast<int>(sizeof...(Args))>{});
        }

    private: /* Class members */
        std::function<void(Ts...)> fn_;
        std::tuple<Ts...> args_;
    };

    /**
     * @brief Helper function to create a worker by which specification of the template arguments aren't necessary.
     */
    template<typename Func, typename... Args>
    worker<Args...> make_worker(Func&& fn, Args&& ... args)
    {
        return worker<Args...>(std::forward<Func>(fn), std::forward<Args>(args)...);
    }
}

#endif

主窗口.cpp

    void main_window::start_worker(worker_object *thread_worker, const worker_callback& on_finish,
                                   const worker_error_callback& on_error)
    {
        auto *worker_thread = new QThread;
        thread_worker->moveToThread(worker_thread);
        connect(thread_worker, &worker_object::error, this, on_error);
        connect(worker_thread, &QThread::started, thread_worker, &worker_object::run);
        connect(thread_worker, &worker_object::finished, worker_thread, &QThread::quit);
        connect(thread_worker, &worker_object::finished, this, on_finish);
        connect(thread_worker, &worker_object::finished, thread_worker, &worker_object::deleteLater);
        connect(worker_thread, &QThread::finished, worker_thread, &QThread::deleteLater);
        worker_thread->start();
    }

main_window.cpp 示例 1:无参数

void main_window::service_example()
{
    //STEP 1: specify a (synchronous) task for the worker.
    auto *work = new worker(make_worker([this]()
    {
        auto task_len_ms = 2500; //ms
        logger_->info("Doing some concurrent work for " + std::to_string(task_len_ms) + " milliseconds...");
        QThread::msleep((unsigned)task_len_ms);
    }));

    //STEP 2: specify the completion handler. Called upon a worker_object::finished signal.
    auto on_finish_callback = [this]()
    {
        logger_->info("Concurrent work finished!");
    };

    //STEP 3: specify an error handler. Called upon a worker_object::error(const QString&) signal.
    auto on_error_callback = [this](const QString& err_msg)
    {
        logger_->error(err_msg.toStdString());
    };

    //STEP 4: start the worker.
    start_worker(work, on_finish_callback, on_error_callback);
}

main_window.cpp 示例 2:一些参数

//STEP 1: specify a (synchronous) task for the worker.
auto *work = new worker(make_worker([this](const std::string& personal_msg, unsigned long wait_time)
{
    logger_->info(personal_msg);
    QThread::msleep((unsigned)wait_time);
}, "Hello, world?", 2500));

//STEP 2: specify the completion handler. Called upon a worker_object::finished signal.
//STEP 3: specify an error handler. Called upon a worker_object::error(const QString&) signal.
//STEP 4: start the worker.
于 2018-09-14T21:31:02.633 回答