指定线程的std :: async模拟

时间:2018-03-27 08:12:03

标签: c++ multithreading qt asynchronous future

我需要使用多个对象,每个操作可能需要很长时间。

无法将处理放在GUI(主)线程中,我在此处启动它。

我需要在主框架中使用on asynchronous operationsstd::asyncstd::future类似的东西QtConcurrent::run()进行所有通信(Qt 5),QFuture等,但它不提供线程选择。我总是需要在一个额外的线程中使用选定的对象(objects == devices),

因为:

  1. 我需要制作一个通用解决方案,并且不希望每个类都是线程安全的
  2. 例如,甚至如果为QSerialPort创建一个线程安全的容器,则无法在多个线程中访问Serial port in Qt
  3.   

    注意:串口总是以独占访问方式打开(也就是说,没有其他进程或线程可以访问已打开的串口)。

    1. 通常,与设备的通信包括发送命令和接收答案。我想在发送请求的地方准确处理每个答案,并且不想使用仅限事件驱动的逻辑。
    2. 所以,我的问题。

      如何实施该功能?

      MyFuture<T> fut = myAsyncStart(func, &specificLiveThread);
      

      必须多次传递一个活动线程。

3 个答案:

答案 0 :(得分:5)

让我在不参考Qt库的情况下回答,因为我不知道它的线程API。

在C ++ 11标准库中,没有直接的方法来重用已创建的线程。线程执行单个功能,只能加入或分离。但是,您可以使用生产者 - 消费者模式实现它。消费者线程需要执行由生产者线程放置在队列中的任务(例如,表示为std::function对象)。所以,如果我是正确的,你需要一个单线程的线程池。

我可以推荐我的C ++ 14线程池实现作为任务队列。它不常用(但是!),但是它用单元测试覆盖并用线程消毒剂多次检查。文档很稀疏,但随时可以在github问题中提出任何问题!

图书馆资料库:https://github.com/Ravirael/concurrentpp

和你的用例:

#include <task_queues.hpp>

int main() {
    // The single threaded task queue object - creates one additional thread.
    concurrent::n_threaded_fifo_task_queue queue(1);

    // Add tasks to queue, task is executed in created thread.
    std::future<int> future_result = queue.push_with_result([] { return 4; });

    // Blocks until task is completed.
    int result = future_result.get();

    // Executes task on the same thread as before.
    std::future<int> second_future_result = queue.push_with_result([] { return 4; });
}

答案 1 :(得分:3)

这是Qt。它的信号槽机制是线程感知的。在辅助(非GUI)线程上,创建一个QObject - 派生类,其中包含execute个插槽。连接到此插槽的信号会将事件封送到该线程。

请注意,此QObject不能成为GUI对象的子级,因为子级需要存在于父级线程中,并且此对象显然不存在于GUI线程中。

您可以使用现有的std::promise逻辑处理结果,就像std::future一样。

答案 2 :(得分:2)

如果你想遵循Active Object方法,这里有一个使用模板的例子:

WorkPackage及其界面仅用于在向量中存储不同返回类型的函数(请参阅后面的ActiveObject::async成员函数):

class IWorkPackage {
    public:
        virtual void execute() = 0;

        virtual ~IWorkPackage() {

        }
};

template <typename R>
class WorkPackage : public IWorkPackage{
    private:
        std::packaged_task<R()> task;
    public:
        WorkPackage(std::packaged_task<R()> t) : task(std::move(t)) {

        }

        void execute() final {
            task();
        }

        std::future<R> get_future() {
            return task.get_future();
        }
};

这是ActiveObject类,它将您的设备作为模板。此外,它有一个向量来存储设备的方法请求和一个接一个地执行这些方法的线程。最后,异步函数用于从设备请求方法调用:

template <typename Device>
class ActiveObject {
    private:
        Device servant;
        std::thread worker;
        std::vector<std::unique_ptr<IWorkPackage>> work_queue;
        std::atomic<bool> done;
        std::mutex queue_mutex;
        std::condition_variable cv;
        void worker_thread() {
            while(done.load() == false) {
                std::unique_ptr<IWorkPackage> wp;
                {
                    std::unique_lock<std::mutex> lck {queue_mutex};

                    cv.wait(lck, [this] {return !work_queue.empty() || done.load() == true;});
                    if(done.load() == true) continue;

                    wp = std::move(work_queue.back());
                    work_queue.pop_back();
                }

                if(wp) wp->execute();
            }
        }
    public:

        ActiveObject(): done(false) {
            worker = std::thread {&ActiveObject::worker_thread, this};
        }

        ~ActiveObject() {
            {
                std::unique_lock<std::mutex> lck{queue_mutex};
                done.store(true);
            }
            cv.notify_one();
            worker.join();
        }

        template<typename R, typename ...Args, typename ...Params>
        std::future<R> async(R (Device::*function)(Params...), Args... args) {
            std::unique_ptr<WorkPackage<R>> wp {new WorkPackage<R> {std::packaged_task<R()> { std::bind(function, &servant, args...) }}};
            std::future<R> fut = wp->get_future();
            {
                std::unique_lock<std::mutex> lck{queue_mutex};
                work_queue.push_back(std::move(wp));
            }
            cv.notify_one();

            return fut;
        }

        // In case you want to call some functions directly on the device
        Device* operator->() {
            return &servant;
        }

};

您可以按如下方式使用它:

ActiveObject<QSerialPort> ao_serial_port;
// direct call:
ao_serial_port->setReadBufferSize(size);
//async call:
std::future<void> buf_future = ao_serial_port.async(&QSerialPort::setReadBufferSize, size);

std::future<Parity> parity_future = ao_serial_port.async(&QSerialPort::parity);

// Maybe do some other work here

buf_future.get(); // wait until calculations are ready
Parity p = parity_future.get(); // blocks if result not ready yet, i.e. if method has not finished execution yet

编辑回答评论中的问题:AO主要是多个读者/作者的并发模式。一如既往,它的使用取决于具体情况。因此,此模式通常用于分布式系统/网络应用程序,例如,当多个客户端从服务器请求服务时。在等待服务器应答时,客户端受益于AO模式,因为它们未被阻止。 在网络应用之外的其他字段中不经常使用此模式的一个原因可能是是线程开销。当为每个活动对象创建一个线程时,如果CPU数量很少并且同时使用了许多活动对象,则会产生大量线程,从而产生线程争用。
我只能猜到为什么人们认为这是一个奇怪的问题:正如你已经发现它确实需要一些额外的编程。也许这就是原因,但我不确定 但我认为这种模式对于其他原因和用途也非常有用。至于你的例子,主线程(以及其他后台线程)需要来自单例的服务,例如某些设备或硬件接口,它们只能以较低的数量提供,计算速度慢并且需要并发访问,而不是阻止等待结果。