16

Nel capitolo "tutorial" 5.3.5.3 del suo libro Il linguaggio di programmazione C++ (4a edizione), Bjarne Stroustrup scrive sulla funzione std::async.Quale limite di std :: async si riferisce a Stroustrup?

C'è un limite evidente: Non pensate nemmeno di usare async() per le attività che il blocco che necessitano di condividere risorse - con async() non si sa nemmeno quanti thread s verrà utilizzato perché è fino a async() decidere in base a ciò che sa sulle risorse di sistema disponibili al momento della chiamata.

Una simile esortazione può essere trovata in the C++11-FAQ on his website.

"Semplice" è l'aspetto più importante della progettazione async()/future; i futures possono anche essere utilizzati con thread in generale, ma nemmeno utilizza async() per avviare attività che eseguono operazioni di I/O, manipola mutex o in altri modi interagiscono con altre attività.

È interessante notare che non approfondisce questa limitazione quando ritorna alle caratteristiche di concorrenza di C++ 11 in maggior dettaglio nel § 42.4.6 del suo libro. Ancora più interessante, in questo capitolo ha effettivamente continua (confronta con la dichiarazione sul suo sito web):

un utilizzo semplice e realistico di async() sarebbe quello di generare un compito di raccogliere l'input da un utente.

The documentation of async on cppreference.com non menziona affatto tali limitazioni.

Dopo aver letto alcune proposte e discussioni che portano allo standard C++ 11 nella sua forma finale (che sfortunatamente non ho accesso a), capisco che async è stato incorporato molto tardi nello standard C++ 11 e c'è stata molta discussione su quanto questa funzione dovrebbe essere potente. In particolare, ho trovato l'articolo Async Tasks in C++11: Not Quite There Yet by Bartosz Milewski un ottimo riassunto dei problemi che devono essere considerati quando si implementa async.

Tuttavia, tutti i problemi discussi sono riportate thread_local variabili che non vengono distrutti se un thread viene riciclato, bloccaporte spuri o violazioni di accesso ai dati se un thread cambia il suo compito metà azione e entrambi i compiti tenere una mutex o recursive_mutex rispettivamente, e così via. Queste sono serie preoccupazioni per gli implementatori della funzionalità, ma se ho capito bene, la specifica corrente di async richiede che tutti questi dettagli siano nascosti all'utente eseguendo l'attività sul thread del chiamante o come se un nuovo thread sia stato creato per il compito.

Quindi la mia domanda è: Cosa non mi è concesso a che fare con async che mi è permesso di fare utilizzando thread s manualmente e qual è il motivo di questa limitazione?

Ad esempio, c'è qualcosa di sbagliato nel seguente programma?

#include <future> 
#include <iostream> 
#include <mutex> 
#include <vector> 

static int tally {}; 
static std::mutex tally_mutex {}; 

static void 
do_work(const int amount) 
{ 
    for (int i = 0; i < amount; ++i) 
    { 
     // Might do something actually useful... 
     const std::unique_lock<std::mutex> lock {tally_mutex}; 
     tally += 1; 
    } 
} 

int 
main() 
{ 
    constexpr int concurrency {10}; 
    constexpr int amount {1000000}; 
    std::vector<std::future<void>> futures {}; 
    for (int t = 0; t < concurrency; ++t) 
    futures.push_back(std::async(do_work, amount/concurrency)); 
    for (auto& future : futures) 
    future.get(); 
    std::cout << tally << std::endl; 
} 

Ovviamente, se il runtime decide di pianificare tutte le attività sul thread principale, ci sarà inutilmente acquisire il mutex più e più volte senza una buona ragione. Ma mentre questo potrebbe essere inefficiente, non è errato.

+0

Come nell'esempio di Stroustrup: non eseguire blocchi, è possibile ottenere facilmente deadlock poiché non è garantito che async venga eseguito in un altro thread. Nel tuo esempio, blocca il mutex nella funzione principale prima di chiamare asincrona. –

+1

@DmitriSosnik Puoi spiegare come esattamente questo potrebbe bloccarsi? Acquisire il mutex una volta nel thread principale prima di avviare le attività non sarà certo un deadlock, ma ridurrà anche l'uso di un mutex ad absurdum e darà a 'tally' un valore spazzatura. Ad ogni modo, la mia domanda è quali sono le limitazioni di 'std :: async', non è il modo migliore per impostare una variabile su 1000000. – 5gon12eder

risposta

5

Il "problema" con std :: async è che per impostazione predefinita non si sa se inizia o meno una discussione. Se la tua funzione deve essere eseguita su un thread separato, questo è un problema poiché la tua funzione potrebbe non funzionare finché non viene chiamato get() o wait(). È possibile passare std :: launch :: async per assicurarsi che la funzione venga avviata sul proprio thread, che è come un file std :: thread che non può essere staccato da BG.

+0

Sì, questo ha senso. Prenderò questa risposta come "fondamentalmente, non ci sono vincoli" perché per me, quel "problema" è in realtà solo la semantica di 'std :: async', quindi riferirsi ad esso come una limitazione è in qualche modo come dire" non usa 'fread' per scrivere su file". Naturalmente, quando si implementa una coppia produttore-consumatore, sarebbe una pessima idea usare 'std :: async' con la politica di avvio predefinita. – 5gon12eder

0

Hai appuntito il problema. I fili possono essere riciclati ... Quindi qualsiasi utilizzo dello storage thread_local è pericoloso.

Il tuo ciclo è solo - ma, come hai detto, potrebbe essere inefficiente.

È possibile richiedere alla lingua di produrre un altro thread utilizzando std :: launch :: async. Ma i thread potrebbero ancora essere riciclati.

+2

Grazie. La cosa di cui sono curioso è che, se comprendo correttamente lo standard, l'implementazione deve comportarsi come se * avesse creato un nuovo thread (o eseguito l'attività direttamente sul thread del chiamante), anche se ne ha effettivamente riciclato uno. Quindi dov'è il punto che è "pericoloso" con 'std :: async' ma sicuro con' std :: thread'? Ma se puoi eseguire il backup della tua affermazione che il costrutto nel mio esempio è conforme allo standard, accetterò la tua risposta. – 5gon12eder

Problemi correlati