繁体   English   中英

指定线程的std :: async模拟

[英]std::async analogue for specified thread

我需要处理多个对象,其中每个操作可能需要很多时间。

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

我需要在异步操作上与某些对象进行所有通信, 类似于在我的主框架( Qt 5 )中与std::futureQtConcurrent::run()进行类似 std::async 操作 ,以及使用QFuture等进行的通信,但是不提供线程选择。 我只需要始终在一个附加线程中使用选定的对象(对象==设备),

因为:

  1. 我需要制定一个通用的解决方案,并且不想让每个类都是线程安全的
  2. 例如, 即使为QSerialPort设置了线程安全的容器,也不能在多个线程中访问Qt中的串行端口

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

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

所以,我的问题。

该功能如何实现?

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

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

让我在不参考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; });
}

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

请注意,此QObject不能是GUI对象的子对象,因为子对象需要生活在其父线程中,而该对象明确地不生活在GUI线程中。

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

如果您想采用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数量少且一次使用许多活动对象,则会导致线程争用。
我只能猜测为什么人们认为这是一个奇怪的问题:正如您已经发现的那样,它确实需要一些其他编程。 也许这就是原因,但我不确定。
但是我认为该模式由于其他原因和用途也非常有用。 例如,在主线程(以及其他后台线程)需要单例服务的情况下,例如某些设备或硬件接口,这些设备或硬件接口数量很少,计算速度较慢,并且需要并发访问,而不会阻塞等待结果。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM