it-swarm.com.de

Kann ich std :: async verwenden, ohne auf die zukünftige Einschränkung zu warten?

Hohes Level
Ich möchte einige Funktionen ohne Rückgabewert in einem asynchronen Modus aufrufen, ohne auf deren Beendigung zu warten. Wenn ich std :: async verwende, wird das zukünftige Objekt erst zerstört, wenn die Task abgeschlossen ist. Dadurch wird der Aufruf in meinem Fall nicht synchronisiert.

Beispiel  

void sendMail(const std::string& address, const std::string& message)
{
    //sending the e-mail which takes some time...
}

myResonseType processRequest(args...)
{
    //Do some processing and valuate the address and the message...

    //Sending the e-mail async
    auto f = std::async(std::launch::async, sendMail, address, message);

    //returning the response ASAP to the client
    return myResponseType;

} //<-- I'm stuck here until the async call finish to allow f to be destructed.
  // gaining no benefit from the async call.

Meine Fragen sind  

  1. Gibt es eine Möglichkeit, diese Einschränkung zu überwinden?
  2. wenn (1) nein ist, sollte ich einmal einen Thread implementieren, der diese "Zombie" -Futures nimmt und darauf wartet?
  3. Ist (1) und (2) nein, gibt es eine andere Option, als nur meinen eigenen Thread-Pool zu erstellen?

Hinweis:
Ich nutze die Option Thread + Detach (von @ galop1n vorgeschlagen) nicht, da das Erstellen eines neuen Threads einen Overhead hat, den ich vermeiden möchte. Bei der Verwendung von std :: async (zumindest bei MSVC) wird ein Inner-Thread-Pool verwendet. 

Vielen Dank.

34
Roee Gavirel

Sie können die Zukunft in ein globales Objekt verschieben. Wenn der Destruktor der lokalen Zukunft ausgeführt wird, muss er nicht warten, bis der asynchrone Thread abgeschlossen ist.

std::vector<std::future<void>> pending_futures;

myResonseType processRequest(args...)
{
    //Do some processing and valuate the address and the message...

    //Sending the e-mail async
    auto f = std::async(std::launch::async, sendMail, address, message);

    // transfer the future's shared state to a longer-lived future
    pending_futures.Push_back(std::move(f));

    //returning the response ASAP to the client
    return myResponseType;

}

N.B. Dies ist nicht sicher, wenn der asynchrone Thread auf lokale Variablen in der Funktion processRequest verweist.

Bei der Verwendung von std::async (zumindest bei MSVC) wird ein Inner-Thread-Pool verwendet.

Das ist eigentlich nicht konform. Der Standard besagt explizit, dass Tasks, die mit std::launch::async ausgeführt werden, wie in einem neuen Thread ausgeführt werden müssen. Daher dürfen lokale Thread-Variablen nicht von einer Task zur anderen bestehen. Normalerweise spielt es jedoch keine Rolle.

13
Jonathan Wakely

warum fangen Sie nicht einfach einen Thread an und trennen sich, wenn Sie sich nicht für den Beitritt interessieren?

std::thread{ sendMail, address, message}.detach();   

std :: async ist an die Lebensdauer von std :: future gebunden, die es zurückgibt, und es gibt keine Alternative dazu.

Wenn std :: future in eine Warteschlange gestellt wird, die von einem anderen Thread gelesen wird, ist derselbe Sicherheitsmechanismus erforderlich wie für einen Pool, der eine neue Aufgabe erhält, wie beispielsweise ein Mutex um den Container.

Die beste Option ist daher ein Thread-Pool, um Aufgaben zu verwenden, die direkt in einer sicheren Thread-Warteschlange abgelegt werden. Und es kommt nicht auf eine konkrete Implementierung an.

Unter einer Thread-Pool-Implementierung, die aufrufbare Argumente und Argumente verwendet, werden die Threads in der Warteschlange poliert. Eine bessere Implementierung sollte Bedingungsvariablen ( coliru ) verwenden:

#include <iostream>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <functional>
#include <string>

struct ThreadPool {
    struct Task {
        virtual void Run() const = 0;
        virtual ~Task() {};
    };   

    template < typename task_, typename... args_ >
    struct RealTask : public Task {
        RealTask( task_&& task, args_&&... args ) : fun_( std::bind( std::forward<task_>(task), std::forward<args_>(args)... ) ) {}
        void Run() const override {
            fun_();
        }
    private:
        decltype( std::bind(std::declval<task_>(), std::declval<args_>()... ) ) fun_;
    };

    template < typename task_, typename... args_ >
    void AddTask( task_&& task, args_&&... args ) {
        auto lock = std::unique_lock<std::mutex>{mtx_};
        using FinalTask = RealTask<task_, args_... >;
        q_.Push( std::unique_ptr<Task>( new FinalTask( std::forward<task_>(task), std::forward<args_>(args)... ) ) );
    }

    ThreadPool() {
        for( auto & t : pool_ )
            t = std::thread( [=] {
                while ( true ) {
                    std::unique_ptr<Task> task;
                    {
                        auto lock = std::unique_lock<std::mutex>{mtx_};
                        if ( q_.empty() && stop_ ) 
                            break;
                        if ( q_.empty() )
                            continue;
                        task = std::move(q_.front());
                        q_.pop();
                    }
                    if (task)
                        task->Run();
                }
            } );
    }
    ~ThreadPool() {
        {
            auto lock = std::unique_lock<std::mutex>{mtx_};
            stop_ = true;
        }
        for( auto & t : pool_ )
            t.join();
    }
private:
    std::queue<std::unique_ptr<Task>> q_;
    std::thread pool_[8]; 
    std::mutex mtx_;
    volatile bool stop_ {};
};

void foo( int a, int b ) {
    std::cout << a << "." << b;
}
void bar( std::string const & s) {
    std::cout << s;
}

int main() {
    ThreadPool pool;
    for( int i{}; i!=42; ++i ) {
        pool.AddTask( foo, 3, 14 );    
        pool.AddTask( bar, " - " );    
    }
}
4
galop1n

Anstatt die Zukunft in ein globales Objekt zu verschieben (und das Löschen nicht verwendeter Futures manuell zu verwalten), können Sie sie tatsächlich in den lokalen Bereich der asynchron aufgerufenen Funktion verschieben.

"Lass die Async-Funktion ihre eigene Zukunft nehmen", sozusagen.

Ich habe mir diesen Template-Wrapper ausgedacht, der für mich funktioniert (getestet unter Windows):

#include <future>

template<class Function, class... Args>
void async_wrapper(Function&& f, Args&&... args, std::future<void>& future,
                   std::future<void>&& is_valid, std::promise<void>&& is_moved) {
    is_valid.wait(); // Wait until the return value of std::async is written to "future"
    auto our_future = std::move(future); // Move "future" to a local variable
    is_moved.set_value(); // Only now we can leave void_async in the main thread

    // This is also used by std::async so that member function pointers work transparently
    auto functor = std::bind(f, std::forward<Args>(args)...);
    functor();
}

template<class Function, class... Args> // This is what you call instead of std::async
void void_async(Function&& f, Args&&... args) {
    std::future<void> future; // This is for std::async return value
    // This is for our synchronization of moving "future" between threads
    std::promise<void> valid;
    std::promise<void> is_moved;
    auto valid_future = valid.get_future();
    auto moved_future = is_moved.get_future();

    // Here we pass "future" as a reference, so that async_wrapper
    // can later work with std::async's return value
    future = std::async(
        async_wrapper<Function, Args...>,
        std::forward<Function>(f), std::forward<Args>(args)...,
        std::ref(future), std::move(valid_future), std::move(is_moved)
    );
    valid.set_value(); // Unblock async_wrapper waiting for "future" to become valid
    moved_future.wait(); // Wait for "future" to actually be moved
}

Ich bin ein wenig überrascht, dass es funktioniert, weil ich dachte, der Destruktor der bewegten Zukunft würde blockieren, bis wir async_wrapper verlassen. Es sollte warten, bis async_wrapper zurückkehrt, aber es wartet genau in dieser Funktion. Logischerweise sollte es ein Deadlock sein, ist es aber nicht.

Ich habe auch versucht, am Ende von async_wrapper eine Zeile hinzuzufügen, um das zukünftige Objekt manuell zu leeren:

our_future = std::future<void>();

Dies blockiert auch nicht.

2
Jan Noha

ich habe keine Ahnung, was ich tue, aber das scheint zu funktionieren:

// :( http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3451.pdf
template<typename T>
void noget(T&& in)
{
    static std::mutex vmut;
    static std::vector<T> vec;
    static std::thread getter;
    static std::mutex single_getter;
    if (single_getter.try_lock())
    {
        getter = std::thread([&]()->void
        {
            size_t size;
            for(;;)
            {
                do
                {
                    vmut.lock();
                    size=vec.size();
                    if(size>0)
                    {
                        T target=std::move(vec[size-1]);
                        vec.pop_back();
                        vmut.unlock();
                        // cerr << "getting!" << endl;
                        target.get();
                    }
                    else
                    {
                        vmut.unlock();
                    }
                }while(size>0);
                // ¯\_(ツ)_/¯
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
        });
        getter.detach();
    }
    vmut.lock();
    vec.Push_back(std::move(in));
    vmut.unlock();
}

es erstellt einen dedizierten Getter-Thread für jeden Zukunftstyp, den Sie darauf werfen (z. B. wenn Sie eine Zukunft und Zukunft angeben, haben Sie 2 Threads. Wenn Sie ihm 100x Zukunft geben, haben Sie immer noch nur 2 Threads). und wenn es eine Zukunft gibt, mit der Sie sich nicht befassen wollen, tun Sie einfach notget(fut); - Sie können auch noget(std::async([]()->void{...})); gut funktionieren, kein Block, so scheint es. warning, do not Versuchen Sie, den Wert aus einer Zukunft zu erhalten, nachdem Sie noget () verwendet haben. das ist wahrscheinlich UB und bittet um Ärger.

0
hanshenrik