Programación asíncrona y multihilo en MQL - página 36

 
Vict:

La única explicación razonable que se me ocurre es el control manual sobre el número de hilos que se ejecutan en el pool (si no confiamos en el async|deferred por defecto) - por ejemplo, si vemos que el sistema está muy cargado, reenviar los trabajos con la bandera async, enviarlos en diferido.

En general, estoy algo decepcionado por la lentitud de async(), voy a crear mi propio pool de hilos ligero, me parece que será mucho más rápido.

En el libro se describe el modo async|deferred por defecto, de manera que si no hay suficientes recursos físicos, la tarea no crea un nuevo hilo, sino que se ejecuta en el hilo principal, bloqueándolo.
Y siempre hay que tener en cuenta el posible bloqueo del hilo principal en el modo por defecto, porque la mayoría de las veces se necesitan soluciones para no bloquear el hilo principal.
Es decir, el modo por defecto cambia automáticamente dónde realizar la tarea en función de la carga de un recurso físico, es decir, el procesador.
Por esta razón, si estamos seguros de que el recurso físico no se sobrecargará, es mejor especificar explícitamente la bandera std::launch::async.
Pero si desborda un recurso físico de los procesadores modernos, tendrá que buscar esos cálculos para alcanzar todo su potencial ))
No puedo decir nada sobre la velocidad, porque todavía estoy estudiando la teoría ))

 
Roman:

Así que si estamos seguros de que el recurso físico no se desbordará, es mejor especificar la bandera explícitamente std::launch::async.
Y para desbordar un recurso físico de los procesadores modernos, se necesita mucho tiempo para encontrar tales cálculos para seleccionar todo el potencial ))

El procesador puede soportar incluso un gran número de hilos, pero es más probable que las capacidades del sistema operativo se conviertan en un cuello de botella. Bueno, no puede multiplicar infinitamente los hilos, tarde o temprano async(lauch::async, ...) se encontrará con una excepción lanzada.

 
Vict:

El procesador puede soportar incluso un gran número de hilos, pero es más probable que las capacidades del sistema operativo se conviertan en un cuello de botella. Bueno, no puede multiplicar infinitamente los hilos, tarde o temprano async(lauch::async, ...) se encontrará con una excepción lanzada.

Sí, siempre hay un límite físico, pero es poco probable que se supere este límite en nuestras tareas para mt5.
También async y future en su valor de retorno devuelven excepciones si se producen, no importa cómo obtengamos este valor, a través de una función lambda, ref() o .get().
Y std::thread en su valor de retorno no puede devolver excepciones.

 
Roman:

Sí, siempre hay un límite físico, pero en nuestras tareas para mt5, es poco probable que vaya más allá de este límite.
También async y future en su valor de retorno devuelven excepciones si se producen, no importa cómo obtengamos este valor, a través de una función lamda, ref() o .get().
Y std::thread en su valor de retorno no puede devolver excepciones.

No creo que debas entusiasmarte demasiado con async. Parece que se ha hecho por conveniencia, pero todo esto parece que realmente golpea el rendimiento. No es por un plus.

Y std::thread en el valor de retorno no puede devolver excepciones.

No siempre es necesario. Pero si lo hace, es una docena de líneas adicionales (aunque funcionará más rápido - sin toda la asignación en la pila).
 
Vict:
Pero si tienes que hacerlo, es una docena de líneas extra (mientras que funcionaría más rápido - sin toda la asignación en la pila).

Para que no quede sin sustento:

#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;
}

Ni siquiera necesitaba una docena. Sí, puedes hacer más cosas de bajo nivel sin todo lo de packaged_task y future, pero la idea es que lanzar excepciones no es algo súper asíncrono, y el hilo no tiene absolutamente nada.

 
Tal vez, desde un punto de vista práctico, a veces valdría la pena alejarse de todos estos cojines y recordar la api de Windows - CreateThread, primitivas de sincronización, funciones intercaladas. De todos modos, los hay. Por supuesto, cuando lo escribes para los vientos. Por qué complicar las cosas cuando MT4|MT5 no tienen tareas tan complicadas que requieran cálculos retrasados, piscinas y demás.
 
Andrei Novichkov:
¿Por qué complicar las cosas cuando no hay tareas complejas para MT4|MT5 que requieran cálculos retardados, pools, etc.?
En realidad hay tareas. MT no tiene la capacidad.
Aquí, todo tipo de piscinas son realmente innecesarias. Las soluciones estándar de multihilo, si se aplican correctamente, son suficientes.
 
Yuriy Asaulenko:
En realidad, los retos están ahí. No hay posibilidades para la MT.
Aquí, todo tipo de piscinas son realmente innecesarias. Las soluciones estándar de multihilo, si se aplican correctamente, son suficientes.
Eso es lo que quiero decir.
 

Chicos, estoy compartiendo mi investigación.

Escribí mi propia piscina de hilo de rodillas. Debo notar que es una versión bastante funcional, se puede pasar cualquier functor con cualquier parámetro, en respuesta se devuelve el futuro, es decir, todos los plushkas en forma de captura de excepción y la espera de la terminación están disponibles. Y es tan bueno comohttps://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";
}

No sé quién y en qué estado escribió std::async, pero mi cosa desarrollada por las rodillas es 4 veces más rápida que la estándar (con 10 hilos de trabajo). Aumentar el número de hilos por encima del número de núcleos sólo me ralentiza. Con un tamaño de pool == número de núcleos (2), async pierde unas 30 veces. Así que es así.

Si quiero agrupar hilos, seguro que no será async estándar )).

 
Vict:

Chicos, estoy compartiendo mi investigación.

Escribí mi propia piscina de hilo en mis rodillas. Debo señalar que es una versión bastante funcional, se puede pasar cualquier functor con cualquier parámetro, devuelve futuro en respuesta, es decir, todos los plushkas en forma de captura de excepción y la espera de la terminación están disponibles. Y lo utilicé así como allí https://www.mql5.com/ru/forum/318593/page34#comment_12700601.

No sé quién y en qué estado escribió std::async, pero mi cosa desarrollada por las rodillas es 4 veces más rápida que la estándar (con 10 hilos de trabajo). Aumentar el número de hilos por encima del número de núcleos sólo me retrasa. Con un tamaño de pool == número de núcleos (2), async pierde unas 30 veces. Así que es así.

Si quiero agrupar hilos, seguro que no será async estándar )).

Gracias por la investigación. Es un buen ejemplo, algo en lo que pensar y de lo que aprender.
Pero de nuestro debate general, la mayoría de nosotros ha llegado a la conclusión de que no se necesita realmente una piscina de flujo.
En mi caso, eso sí, desde que me he dado cuenta de que el pool es estático en cuanto a número de hilos, no me funciona.
Pero sí, cuando necesite una piscina, su ejemplo será el adecuado. Gracias por mostrar ejemplos.
Todavía estoy cogiendo el tranquillo ))


Razón de la queja: