Асинхронное и многопоточное программирование в MQL - страница 36

 
Vict:

Единственное разумное объяснение, которое приходит мне в голову - ручной контроль за количеством работающих потоков в пуле (если не доверяем дефолтному async|deferred) - например, видим что система сильна загружена, пересатаём отправлять задания с флагом async, шлём deferred.

Ну а в целом я несколько разочарован тормознутостью async(), будет время состряпаю свой легковесный пул потоков, мне кажется, что это будет много быстрее.

Дефолтный режим async|deferred, в книге описан так, что если в моменте не хватает физических ресурсов, то задача не создаёт новый поток, а исполняется в главном потоке, блокируя его.
И о возможной блокировке главного потока в дефолтом режиме нужно всегда помнить, так как в основном наоборот нужны решения чтобы не блочить основной поток.
То есть дефолтный режим автоматически переключается, где исполнять задачу, в зависимости от нагруженности физического ресурса, то есть процессора.
По этому если мы уверены, что физический ресурс не будет переполнен, лучше указывать флаг явно std::launch::async.
А переполнить физический ресурс современных процессоров, это ещё поискать нужно такие расчёты чтобы выбрать весь потенциал ))
Про скорость пока не могу не чего сказать, так как пока в изучении теории ))

 
Roman:

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

Процессор то вытерпит хоть вагон потоков, а вот возможности ОС - скорее они станут узким местом. Ну не может она бескончено плодить потоки, рано или поздно async(lauch::async, ...) нарвётся на выброшенное исключение.

 
Vict:

Процессор то вытерпит хоть вагон потоков, а вот возможности ОС - скорее они станут узким местом. Ну не может она бескончено плодить потоки, рано или поздно async(lauch::async, ...) нарвётся на выброшенное исключение.

Да, физический предел он присутствует всегда, но в наших задачах для мт5, маловероятно выскочить за этот предел.
Так же async и future в своём возвращаемом значении возвращают исключения если они возникнут, не зависимо как мы это значение получаем, через лямда-функцию, ref() или .get().
А std::thread в возвращаемом значении не умеет возвращать исключения.

 
Roman:

Да, физический предел он присутствует всегда, но в наших задачах для мт5, маловероятно выскочить за этот предел.
Так же async и future в своём возвращаемом значении возвращают исключения если они возникнут, не зависимо как мы это значение получаем, через лямда-функцию, ref() или .get().
А std::thread в возвращаемом значении не умеет возвращать исключения.

Имхо, не стоит уж сильно обольщаться async'ом. Вроде для удобства делали, но все эти плюшки похоже конкретно бьют по производительности. Не по плюсовому это ...

А std::thread в возвращаемом значении не умеет возвращать исключения.

Это не всегда нужно. Но а если нужно, то это десяток лишних строк (при том, что это будет быстрее работать - без всяких аллокаций в куче).
 
Vict:
 Но а если нужно, то это десяток лишних строк (при том, что это будет быстрее работать - без всяких аллокаций в куче).

Чтобы не быть голословным:

#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(3s); 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;
}

Даже десятка не понадобилось. Да, можно еще более низкоуровнего без всяких там packaged_task и future, но идея - переброс исключений - это не какая-то супер фича async, а у thread совсем никак.

 
Возможно, с практической точки зрения, иной раз стоило бы уйти от всех этих плюшек и вспомнить про виндовское api - CreateThread, примитивы синхронизации, функции interlocked. Все же есть. Конечно в том случае, когда пишется для виндов. Зачем усложнять, если для MT4|MT5 нет настолько сложных задач, требующих отложенных вычислений, пулов и проч.
 
Andrei Novichkov:
Зачем усложнять, если для MT4|MT5 нет настолько сложных задач, требующих отложенных вычислений, пулов и проч.
Вообще-то, задачи есть. Возможностей у МТ нет.
Вот, всяческие пулы - это действительно лишнее. Стандартных решений многопоточности при их правильном применении за глаза хватает.
 
Yuriy Asaulenko:
Вообще-то, задачи есть. Возможностей у МТ нет.
Вот, всяческие пулы - это действительно лишнее. Стандартных решений многопоточности при их правильном применении за глаза хватает.
Ну и я про это.
 

Ребята, делюсь исследованием.

Написал я свой пул потоков на коленках. Надо отметить - это вполне функциональная версия, можно передать любой функтор, с любыми параметрами, в ответ возвращается future, т.е. все плюшки в виде отлова исключений и ожидания завершения имеются. И заюзал его как и там https://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";
}

Я не знаю, кто и в каком состоянии писал std::async, но моя поделка на коленках быстрее стандартной в около 4 раз (при 10 работающих потоках). При чём увеличение потоков более количества ядер даёт лишь замедление. При размере пула == количеству ядер (2), async проигрывает в около 30 раз. Вот такие дела.

Если мне захочется пул потоков, то это точно будет не стандартный async )).

 
Vict:

Ребята, делюсь исследованием.

Написал я свой пул потоков на коленках. Надо отметить - это вполне функциональная версия, можно передать любой функтор, с любыми параметрами, в ответ возвращается future, т.е. все плюшки в виде отлова исключений и ожидания завершения имеются. И заюзал его как и там https://www.mql5.com/ru/forum/318593/page34#comment_12700601

Я не знаю, кто и в каком состоянии писал std::async, но моя поделка на коленках быстрее стандартной в около 4 раз (при 10 работающих потоках). При чём увеличение потоков более количества ядер даёт лишь замедление. При размере пула == количеству ядер (2), async проигрывает в около 30 раз. Вот такие дела.

Если мне захочется пул потоков, то это точно будет не стандартный async )).

Спасибо за исследование. Хороший пример, есть над чем подумать для изучения.
Но из общего нашего обсуждения, большинство из нас пришли к выводу, что пул потоков по сути то и не особо нужен.
В моём случае это точно, так как понял что пул статичный по количеству потоков, мне это не подходит.
А так да, когда нужен будет пул, то ваш пример как раз к стати будет. Спасибо что показываете примеры.
Я пока с простенького понимания начинаю разбираться ))


Причина обращения: