【问题标题】:Signaling main thread when std::future is ready to be retrieved当 std::future 准备好被检索时向主线程发出信号
【发布时间】:2017-08-24 02:56:53
【问题描述】:

我正在尝试了解std::asyncstd::future 系统。我不太明白的是你如何处理运行多个异步“任务”,然后根据返回的第一个、第二个等,运行一些额外的代码。

示例:假设您的主线程处于一个简单的循环中。现在,根据用户输入,您通过std::async 运行多个函数,并将期货保存在std::list 中。

我的问题是,我如何从std::async 函数传回可以指定哪个未来已完成的信息?

我的主线程基本上处于消息循环中,我需要做的是让std::async 运行的函数能够将一条消息排队,该消息以某种方式指定哪个未来已完成。问题是该函数无法访问未来。

我只是错过了什么吗?

这是我想要完成的一些伪代码;如果有办法也有办法使用取消令牌调用“取消”请求,则可以加分。

class RequestA
{
public:
    int input1;

    int output1;
};

main()
{
    while(1)
    {
       //check for completion
       // i.e. pop next "message"
       if(auto *completed_task = get_next_completed_task())
       {
          completed_task->run_continuation();
       }

       // other code to handle user input
       if(userSaidRunA())
       {
          // note that I don't want to use a raw pointer but
          // am not sure how to use future for this
          RequestA *a = new RequestA();
          run(a, OnRequestTypeAComplete);
       }

    }
}

void OnRequestTypeAComplete(RequestA &req)
{
    // Do stuff with req, want access to inputs and output
}

【问题讨论】:

  • 您可以将 callable 传递给正在执行的线程,该线程会将消息放入队列中。
  • std::asyncCallable 对象进行操作。例如,您可以在创建该对象时为其分配一个 ID 号,并将该 ID 与 std::future 对象一起存储在列表中。然后Callable 可以将其 ID 发布到消息队列,消息处理程序可以在列表中查找 ID。另一种选择是首先将一个空的std::future 添加到列表中,然后将该对象的迭代器传递给Callable,以便它可以将其发送回消息队列。将std::async 的结果移动到现有的std::future 对象,并让消息处理程序使用发布的迭代器
  • 你可能想看看例如HPX 库 (stellar-group.org/category/hpx)。标准库目前没有针对这些问题的优雅解决方案。特别是,HPX 通过then() 为期货提供延续,并通过when_any()/wait_any() 等待一系列期货中的任何一个。

标签: c++ asynchronous future


【解决方案1】:

不幸的是,C++11 std::future 不提供延续和取消。您只能从std::future 检索结果一次。此外,future 从其析构函数中的std::async 块返回。有一个由 Adob​​e 的 Sean Parent 领导的小组。他们按照应有的方式实现了futureasynctask。还具有像when_allwhen_any 这样的延续功能。可能是你正在寻找的东西。无论如何看看this project。代码质量好,易于阅读。

如果平台相关的解决方案也适合您,您可以检查它们。对于 Windows,我知道 PPL 库。它还具有取消和继续的原语。

【讨论】:

  • 问题是 when_any 对继续或取消没有帮助。我看过 PPL,但我认为它并不真正适合我想要做的事情。
【解决方案2】:

您可以创建一个包含标志的struct,并将对该标志的引用传递给您的线程函数。

有点像这样:

int stuff(std::atomic_bool& complete, std::size_t id)
{
    std::cout << "starting: " << id << '\n';

    // do stuff
    std::this_thread::sleep_for(std::chrono::milliseconds(hol::random_number(3000)));

    // generate value
    int value = hol::random_number(30);

    // signal end
    complete = true;
    std::cout << "ended: " << id << " -> " << value << '\n';

    return value;
}

struct task
{
    std::future<int> fut;
    std::atomic_bool complete;

    task() = default;
    task(task&& t): fut(std::move(t.fut)), complete(t.complete.load()) {}
};

int main()
{
    // list of tasks
    std::vector<task> tasks;

    // reserve enough spaces so that nothing gets reallocated
    // as that would invalidate the references to the atomic_bools
    // needed to signal the end of a thread
    tasks.reserve(3);

    // create a new task
    tasks.emplace_back();

    // start it running
    tasks.back().fut = std::async(std::launch::async, stuff, std::ref(tasks.back().complete), tasks.size());

    tasks.emplace_back();
    tasks.back().fut = std::async(std::launch::async, stuff, std::ref(tasks.back().complete), tasks.size());

    tasks.emplace_back();
    tasks.back().fut = std::async(std::launch::async, stuff, std::ref(tasks.back().complete), tasks.size());

    // Keep going as long as any of the tasks is incomplete
    while(std::any_of(std::begin(tasks), std::end(tasks),
        [](auto& t){ return !t.complete.load(); }))
    {

        // do some parallel stuff
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }

    // process the results

    int sum = 0;
    for(auto&& t: tasks)
        sum += t.fut.get();

    std::cout << "sum: " << sum << '\n';
}

【讨论】:

  • 这不是我想要做的。我想要做的是在不遍历所有任务的情况下确定完成了哪个任务。我的一个想法是让异步调用的结尾入队一个具有延续 Lamba 的结构,它可能包含“请求”的一些状态
  • @bpelkes 我不确定标准库能否以简洁的方式做到这一点,请参阅stackoverflow.com/questions/19225372/…
【解决方案3】:

这里使用std::unordered_map 而不是std::list 的解决方案,您无需修改​​callables。取而代之的是,您使用一个辅助函数,该函数为每个 task 分配一个 id 并在它们完成时通知:

class Tasks {
public:
    /*
     *  Helper to create the tasks in a safe way.
     *  lockTaskCreation is needed to guarantee newTask is (temporarilly)
     *  assigned before it is moved to the list of tasks
     */
    template <class R, class ...Args>
    void createNewTask(const std::function<R(Args...)>& f, Args... args) {
        std::unique_lock<std::mutex> lock(mutex);
        std::lock_guard<std::mutex> lockTaskCreation(mutexTaskCreation);
        newTask = std::async(std::launch::async, executeAndNotify<R, Args...>,
            std::move(lock), f, std::forward<Args>(args)...);
    }

private:
    /*
     *  Assign an id to the task, execute it, and notify when finishes
     */
    template <class R, class ...Args>
    static R executeAndNotify(std::unique_lock<std::mutex> lock,
        const std::function<R(Args...)>& f, Args... args)
    {
        {
            std::lock_guard<std::mutex> lockTaskCreation(mutexTaskCreation);
            tasks[std::this_thread::get_id()] = std::move(newTask);
        }
        lock.unlock();
        Notifier notifier;
        return f(std::forward<Args>(args)...);
    }

    /*
     *  Class to notify when a task is completed (follows RAII)
     */
    class Notifier {
    public:
        ~Notifier() {
            std::lock_guard<std::mutex> lock(mutex);
            finishedTasks.push(std::this_thread::get_id());
            cv.notify_one();
        }
    };

    /*
     *  Wait for a finished task.
     *  This function needs to be called in an infinite loop
     */
    static void waitForFinishedTask() {
        std::unique_lock<std::mutex> lock(mutex);
        cv.wait(lock, [] { return finishedTasks.size() || finish; });
        if (finishedTasks.size()) {
            auto threadId = finishedTasks.front();
            finishedTasks.pop();
            auto result = tasks.at(threadId).get();
            tasks.erase(threadId);
            std::cout << "task " << threadId
                << " returned: " << result << std::endl;
        }
    }

    static std::unordered_map<std::thread::id, std::future<int>> tasks;
    static std::mutex mutex;
    static std::mutex mutexTaskCreation;
    static std::queue<std::thread::id> finishedTasks;
    static std::condition_variable cv;
    static std::future<int> newTask;

    ...
};

...

那么,你可以这样调用异步任务:

int doSomething(int i) {
    std::this_thread::sleep_for(std::chrono::milliseconds(1));
    return i;
}

int main() {
    Tasks tasks;
    tasks.createNewTask(std::function<decltype(doSomething)>(doSomething), 10);
    return 0;
}

See a complete implementation run on Coliru

【讨论】:

    猜你喜欢
    • 2014-04-02
    • 2018-04-29
    • 2018-01-06
    • 1970-01-01
    • 1970-01-01
    • 2013-10-05
    • 1970-01-01
    • 1970-01-01
    • 2015-01-03
    相关资源
    最近更新 更多