Programmazione asincrona e multithread in MQL - pagina 36

 
Vict:

L'unica spiegazione ragionevole che mi viene in mente è il controllo manuale del numero di thread in esecuzione nel pool (se non ci fidiamo del default async|deferred) - per esempio, se vediamo che il sistema è molto carico, reinviare i lavori con il flag async, inviarli in differita.

In generale, sono un po' deluso dalla lentezza di async(), creerò il mio pool di thread leggero, mi sembra che sarà molto più veloce.

La modalità predefinita async|deferred è descritta nel libro in modo che se non ci sono abbastanza risorse fisiche, il compito non crea un nuovo thread, ma lo esegue nel thread principale, bloccandolo.
E dovreste sempre essere consapevoli del possibile blocco del thread principale in modalità predefinita, perché la maggior parte delle volte avete bisogno di soluzioni per non bloccare il thread principale.
Cioè, la modalità predefinita cambia automaticamente dove eseguire il compito a seconda del carico di una risorsa fisica, cioè il processore.
Per questo motivo, se siamo sicuri che la risorsa fisica non sarà sovraccaricata, è meglio specificare esplicitamente il flag std::launch::async.
Ma se si sfora una risorsa fisica di processori moderni, si dovrà cercare tali calcoli per raggiungere il suo pieno potenziale ))
Non posso dire nulla sulla velocità, perché sto ancora studiando la teoria ))

 
Roman:

Quindi, se siamo sicuri che la risorsa fisica non sarà sovraccaricata, è meglio specificare il flag esplicitamente std::launch::async.
E per sovraccaricare una risorsa fisica dei processori moderni, ci vuole molto tempo per trovare tali calcoli per selezionare tutti i potenziali ))

Il processore può sopportare un numero anche grande di thread, ma è più probabile che le capacità del sistema operativo diventino un collo di bottiglia. Beh, non può moltiplicare all'infinito i thread, prima o poi async(lauch::async, ...) si imbatterà in un'eccezione lanciata.

 
Vict:

Il processore può sopportare anche un gran numero di thread, ma è più probabile che le capacità del sistema operativo diventino un collo di bottiglia. Beh, non può moltiplicare all'infinito i thread, prima o poi async(lauch::async, ...) si imbatterà in un'eccezione lanciata.

Sì, c'è sempre un limite fisico, ma è improbabile superare questo limite nei nostri compiti per mt5.
Anche async e future nel loro valore di ritorno restituiscono eccezioni se si verificano, non importa come otteniamo questo valore, tramite una funzione lambda, ref() o .get().
E std::thread nel suo valore di ritorno non può restituire eccezioni.

 
Roman:

Sì, c'è sempre un limite fisico, ma nei nostri compiti per mt5, è improbabile andare oltre questo limite.
Anche async e future nel loro valore di ritorno restituiscono eccezioni se si verificano, non importa come otteniamo questo valore, attraverso una funzione lamda, ref() o .get().
E std::thread nel suo valore di ritorno non può restituire eccezioni.

Non credo che dovresti eccitarti troppo con l'async. Sembra essere stato fatto per comodità, ma tutta questa roba sembra davvero colpire le prestazioni. Non è da un più uno.

E std::thread nel valore di ritorno non può restituire eccezioni.

Non è sempre necessario. Ma se lo fai, sono una dozzina di righe in più (anche se funzionerà più velocemente - senza tutta l'allocazione nel mucchio).
 
Vict:
Ma se devi farlo, sono una dozzina di righe in più (mentre funzionerebbe più velocemente - senza tutta l'assegnazione nel mucchio).

Per non essere infondato:

#include <thread>
#include <future>
#include <iostream>
#include <chrono>
using namespace std;

template <typename T>
void thread_fn(T &&task) {task(0);}

int main()
{
   packaged_task<int(int)> task{ [](int i){this_thread::sleep_for(3 s); return i==0?throw 0: i;} };
   auto f = task.get_future();
   thread t{thread_fn<decltype(task)>, move(task)};
   t.detach();

   try {
      cout << f.get() << endl;
   }catch(...) {
      cout << "exception caught" << endl;
   }

   return 0;
}

Non ne serviva nemmeno una dozzina. Sì, si possono fare anche più cose di basso livello senza tutta la roba packaged_task e future, ma l'idea è che lanciare eccezioni non è una cosa super asincrona, e il thread non ha assolutamente nulla.

 
Forse, da un punto di vista pratico, a volte varrebbe la pena di allontanarsi da tutti questi cuscini e ricordare le api di Windows - CreateThread, primitive di sincronizzazione, funzioni interbloccate. Comunque sia, ci sono. Naturalmente, quando si scrive per i venti. Perché complicare le cose quando MT4|MT5 non hanno compiti così complicati che richiedono calcoli ritardati, pool e così via.
 
Andrei Novichkov:
Perché complicare le cose quando non ci sono compiti complessi per MT4|MT5 che richiedono calcoli ritardati, pool ecc.
In realtà ci sono dei compiti. MT non ha le capacità.
Qui, tutti i tipi di piscine sono davvero inutili. Le soluzioni standard di multi-threading, se applicate correttamente, sono sufficienti.
 
Yuriy Asaulenko:
In realtà, le sfide ci sono. Non ci sono possibilità per MT.
Qui, tutti i tipi di piscine sono davvero inutili. Le soluzioni standard di multithreading, se applicate correttamente, sono sufficienti.
Questo è quello che voglio dire.
 

Ragazzi, sto condividendo la mia ricerca.

Ho scritto la mia piscina di fili sulle mie ginocchia. Dovrei notare che è una versione abbastanza funzionale, si può passare qualsiasi funtore con qualsiasi parametro, in risposta viene restituito il futuro, cioè sono disponibili tutti i plushka sotto forma di cattura delle eccezioni e attesa della terminazione. E l'ho usato così come cihttps://www.mql5.com/ru/forum/318593/page34#comment_12700601.

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

mutex mtx;
set<thread::id> id;
atomic<unsigned> atm{0};

int main()
{
   Thread_pool p{10};
   for (int i = 0;  i < 10000;  ++ i) {
      vector<future<void>> futures;
      for (int i = 0; i < 10; ++i) {
         auto fut = p.push([]{
                              ++ atm;
                              lock_guard<mutex> lck{mtx};
                              id.insert( this_thread::get_id() );
                           });
         futures.push_back(move(fut));
      }
   }
   cout << "executed " << atm << " tasks, by " << id.size() << " threads\n";
}

Non so chi e in quale stato ha scritto std::async, ma la mia cosa sviluppata in ginocchio è 4 volte più veloce di quella standard (con 10 thread funzionanti). Aumentare il numero di thread rispetto al numero di core mi rallenta soltanto. Con dimensione del pool == numero di core (2), async perde di circa 30 volte. Quindi è così.

Se voglio mettere in comune i thread, non sarà async standard di sicuro )).

 
Vict:

Ragazzi, sto condividendo la mia ricerca.

Ho scritto la mia piscina di fili sulle mie ginocchia. Dovrei notare che è una versione abbastanza funzionale, si può passare qualsiasi funtore con qualsiasi parametro, in risposta viene restituito il futuro, cioè sono disponibili tutti i plushka sotto forma di cattura delle eccezioni e attesa della terminazione. E l'ho usato così come ci https://www.mql5.com/ru/forum/318593/page34#comment_12700601

Non so chi e in quale stato ha scritto std::async, ma la mia cosa sviluppata in ginocchio è 4 volte più veloce di quella standard (con 10 thread funzionanti). Aumentare il numero di thread rispetto al numero di core mi rallenta soltanto. Con dimensione del pool == numero di core (2), async perde di circa 30 volte. Quindi è così.

Se voglio mettere in comune i thread, non sarà async standard di sicuro )).

Grazie per la ricerca. È un buon esempio, qualcosa a cui pensare e da cui imparare.
Ma dalla nostra discussione generale, la maggior parte di noi è giunta alla conclusione che una piscina di flusso non è veramente necessaria.
Nel mio caso, questo è sicuro, da quando ho capito che il pool è statico in termini di numero di thread, non funziona per me.
Ma sì, quando avrò bisogno di una piscina, il tuo esempio sarà giusto. Grazie per aver mostrato degli esempi.
Ci sto ancora prendendo la mano ))