Как хранить самоуничтожающиеся фьючерсы в списке - PullRequest
7 голосов
/ 18 марта 2019

У меня есть некоторые задачи, которые нужно выполнять асинхронно, и сервер не может закрыться, пока еще есть задачи.Поэтому я пытаюсь сохранить в списке фьючерсы, возвращаемые std::async, но я также не хочу получать их бесконечно растущий список.Поэтому я хочу удалить фьючерсы по мере их завершения.

Вот примерно то, что я пытаюсь сделать:

// this is a member of the server class
std::list<std::future<void>> pending;

std::list<std::future<void>>::iterator iter = ???;

pending.push_back( std::async( std::launch::async, [iter]()
{
    doSomething();
    pending.remove( iter );
} );

Здесь iter необходимо указать на недавновставлен элемент, но я не могу получить его до вставки элемента (итератор отсутствует) или после него (поскольку он передается в лямбда-выражение по значению).Я мог бы сделать shared_ptr для хранения итератора, но это кажется излишним.

Есть ли лучший шаблон для этого?

Обновление: там, кажетсябыть еще одной проблемой с этим.Когда будущее пытается удалить себя из списка, оно, по сути, ждет своего завершения, что блокирует все.Упс!

Кроме того, деструктор списка очищает список до вызова деструкторов элемента.

Ответы [ 2 ]

7 голосов
/ 18 марта 2019

Похоже, вы можете просто добавить значение по умолчанию std::future в список, получить к нему итератор, а затем переместить ваше будущее.

Имейте в виду, этот не-мьютекс-защищенный remove(iter) выглядит ужасно опасно.

1 голос
/ 18 марта 2019

Вот один из способов. Я не думаю, что это нужно фьючерсам:

#include <unordered_set>
#include <condition_variable>
#include <mutex>
#include <thread>

struct server
{
    std::mutex pending_mutex;
    std::condition_variable pending_condition;
    std::unordered_set<unsigned> pending;
    unsigned next_id =  0;

    void add_task()
    {
        auto lock = std::unique_lock(pending_mutex);
        auto id = next_id++;
        auto t = std::thread([this, id]{
            this->doSomething();
            this->notify_complete(id);
        });
        t.detach(); // or we could store it somewhere. e.g. pending could be a map
        pending.insert(id);
    }

    void doSomething();

    void notify_complete(unsigned id)
    {
        auto lock = std::unique_lock(pending_mutex);
        pending.erase(id);
        if (pending.empty())
            pending_condition.notify_all();
    }

    void wait_all_complete()
    {
        auto none_left = [&] { return pending.empty(); };

        auto lock = std::unique_lock(pending_mutex);
        pending_condition.wait(lock, none_left);
    }
};


int main()
{
    auto s = server();
    s.add_task();
    s.add_task();
    s.add_task();
    s.wait_all_complete();
}

Вот с фьючерсами, на случай, если это важно:

#include <unordered_map>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <future>

struct server
{
    std::mutex pending_mutex;
    std::condition_variable pending_condition;
    std::unordered_map<unsigned, std::future<void>> pending;
    unsigned next_id =  0;

    void add_task()
    {
        auto lock = std::unique_lock(pending_mutex);
        auto id = next_id++;
        auto f = std::async(std::launch::async, [this, id]{
            this->doSomething();
            this->notify_complete(id);
        });
        pending.emplace(id, std::move(f));
    }

    void doSomething();

    void notify_complete(unsigned id)
    {
        auto lock = std::unique_lock(pending_mutex);
        pending.erase(id);
        if (pending.empty())
            pending_condition.notify_all();
    }

    void wait_all_complete()
    {
        auto none_left = [&] { return pending.empty(); };

        auto lock = std::unique_lock(pending_mutex);
        pending_condition.wait(lock, none_left);
    }
};


int main()
{
    auto s = server();
    s.add_task();
    s.add_task();
    s.add_task();
    s.wait_all_complete();
}

Вот список версий:

#include <list>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <future>

struct server
{
    using pending_list = std::list<std::future<void>>;
    using id_type = pending_list::const_iterator;

    std::mutex pending_mutex;
    std::condition_variable pending_condition;
    pending_list pending;

    void add_task()
    {
        auto lock = std::unique_lock(pending_mutex);

        // redundant construction
        auto id = pending.emplace(pending.end());
        auto f = std::async(std::launch::async, [this, id]{
            this->doSomething();
            this->notify_complete(id);
        });
        *id = std::move(f);
    }

    void doSomething();

    void notify_complete(id_type id)
    {
        auto lock = std::unique_lock(pending_mutex);
        pending.erase(id);
        if (pending.empty())
            pending_condition.notify_all();
    }

    void wait_all_complete()
    {
        auto none_left = [&] { return pending.empty(); };

        auto lock = std::unique_lock(pending_mutex);
        pending_condition.wait(lock, none_left);
    }
};


int main()
{
    auto s = server();
    s.add_task();
    s.add_task();
    s.add_task();
    s.wait_all_complete();
}
...