Pull to refresh

Comments 21

Есть возможность сравнить на вашем же железе с реализацией std::async от Microsoft? В MSVC async работает поверх Parallel Patterns Library и не порождает много лишних потоков.
У меня получились другие результаты:
MSVC v12:
test_act 327.7 OK
test_async 306.7 OK

GCC 4.8.1
test_act 379.4 OK
test_async 337.2 OK

Параметры:
#define N_SIZE 10000000
#define N_THREADS 7
#define N_PARALLEL 73

Судя по этим результатам, стандартная реализация всегда чуть лучше.
На моей машине оптимальным значением N_THREADS было количество на единицу меньше, чем процессоров. При большем количестве — стандартная реализация быстрее.
Все равно стандартная реализация быстрее:

MSVC v12:
N=100000000
test_act 3356 OK
test_async 2989 OK

N=10000000
test_act 327.2 OK
test_async 290.1 OK

GCC 4.8.1:
N=100000000
test_act 3330 OK
test_async 3120 OK

N=10000000
test_act 330.2 OK
test_async 309.6 OK

Параметры:
#define N_THREADS 3
#define N_PARALLEL 73
Ошибки:
1. Вы принудительно заставили запускать async задачи в отдельных потоках. В вашей задаче нет смысла запускать потоков больше, чем кол-во процессоров в системе. Не стоит принудительно пихать
2. Следовало разобраться почему SIGSEG при захвате в лямбде, а не пихать tuple, код был бы заметно короче, для этого надо использовать std::decay.
3. Опять же нарушаете стилистику, чем вам std::future не угодил, а точнее std::promise, интерфейс был бы стандартный, да не изобретали бы велосипед с возвратом значения.
4. просто ужасный рабочий цикл, чрезмерно переусложнён.

По коду вывод один: Нужно знать не только объекты стандартной библиотеки, но и уметь ими правильно пользоваться. А вот с использованием у вас просто никак.

Правильнее было бы сделать отдельно очередь с 3мя методами: void push(task), bool pop(task&), void notify_exit(). notify_exit пробуждает все потоки в очереди и они возвращают false иначе pop висит до посинения, пока не придет задача. И не пришлось бы городить такой сложный огород.
А я не понял, в каком месте там sigsegv при захвате? Неявный захват this?
Автор, все же оно называется лямбда-выражение, а разворачивается в замыкание.
При создании задачи.
some_type some_object;
control << act::make_task([&some_object](){some_object.some_method();}); 
control << act::make_task([some_object](){some_object.some_method();}); 

SIGSEGV При вызове t->invoke();
Если передавать строки, то там мусор. Хотя указатель правильный.

Но так работает.
some_type some_object;
control << act::make_task([](some_type & some_object){some_object.some_method();}, std::ref(some_object)); 


Не было времени продебажить этот момент.
Тут в обоих случаях undefined behaviour, если ссылка живет дольше чем объект. Просто во втором случае повезло.
Объект, естественно, существует до завершения задачи.
1. Логично что, лучше запускать async «пачками» по количеству процессоров. Я хотел сделать свою библиотеку, чтобы не придавать этому значение.
2. Напоминаю, что это just 4 fun проект. Я пользовался знакомыми мне сущностями. Я не писал до этого на C++11.
3. Я и не против std::future. Все уже написано до меня, но я сделал это вопреки.
4. Огород не сложный, он просто не изящный.

Спасибо за замечания, я писал статью именно для этого.
У вас проблема в том, что вы берете ссылки как есть, а они у вас дохнут еще до начала выполнения задачи, вот вам и SIGSEGV. Используя же std::decay вы убережете себя от этого. Так же и правильно отработают rvalue, значения которых 100% не доживёт до начала выполнения задачи.

В вашем случае что будет, если сигнатура функции будет содержать const std::string& и вы прокинете строку? Естественно SIGSEGV, ибо объект строки сдохнет после добавления в очередь, а вот на этапе выполнения ссылка уже будет мертва.

Так что изучать всё же стоит сразу нормально, а биться головой о стену, в надежде, что в нужный момент ваш код не упадёт.
По поводу этого:
template <typename T, typename ... Args>
  std::shared_ptr<task<decltype(&T::operator())>> make_task(T func, Args ... args )
  {
    return std::shared_ptr<task<decltype(&T::operator())>>(new task<decltype(&T::operator())>(func, args ...));
  }

Зачем использовать decltype(&T::operator()) в двух местах, если же выше у Вас написано:
template <typename T>
  class task
      : public task<decltype(&T::operator())>
  {
  };


Или я что-то не понял?
Точнее, даже в трёх
Здесь должен был быть make_shared
Главный цикл дочернего потока контролируется атомарной переменной (по идее достаточно было объявить ее volatile, так как тут нет состояния гонки, главный поток в нее только пишет, а дочерние только читают)
volatile в C++ никак не связан с многопоточностью и атомарностью.
Состояние гонки всегда есть, если в общие данные пишет хотя бы один из потоков, когда другие их читают или пишут. Помимо этого, стандартные атомики (не relaxed) влекут за собой acquire-release семантику и устраняют memory reordering, так что они не только ради атомарности нужны.
особой связи с мульти-тредингом я как-то в коде не вижу — наверно около 80% кода имплементят std::function.
оставшееся thread pool (который называется почему-то control).
думаю чтобы было ОК для «изучаем C++11» надо сделать следющее:
1) все что связано с «описаниями» тасков перевести на std::function, лямбды никуда от этого не пропадут и опыт их использования. написание своего std::function — это целая тема и не связана с асинхронностью.
2) выпилить std::cond и std::mutex для каждой задачи, список тасков может быть просто списком std::function. создавать тысячи или миллионы мутексов — как минимум странно, а в реальной жизни уж очень накладно.
3) попробывать упростить класс control, в нем «много пены»
4) упростить main — зачем ждать окончания каждого таска индивидуально? ждите пока тред пул не станет пустым или расширте интерфейс и дайте таскам приоритет/порядок исполнения. иначе получается что логика тред пула перекочевала в main.

в результате код уменьшится раз так в 5 и C++11 усвоится лутше. а тему «как самому сделать std::function» лутше изучать отдельно, там выше крыше своих хитростей.
Поддерживаю этот комментарий, сам сюда зашел, чтобы оставить подобный же, согласен с 1, 3 и 4 пунктами. Про мьютексы вообще не подумал.
GCD прекрасен, но портирован только на FreeBSD и Linux, и привязывает разработчика к clang. Не всем такое подойдёт.
Примерно с появлением С++11 я ушел из мира С++ в .net (так получилось, производственная необходимость). Холиварить о языках не хочу, у меня просто вопрос. Сколько смотрю на реализацию лямбд, асинхронности и прочих плюшек в С++, не могу не отметить, что все это несопоставимо более громоздко выглядит, чем в C#. Вопрос к тем, кто имел возможность поработать и с тем, и с тем: в С++ оно действительно нужно? Может быть у этого языка должен быть просто другой путь развития? Или всем ОО-языкам предстоит пройти одни и те же ступени (например лямбды есть в Python и Java 8)? Есть хотя бы в теории альтернатива лямбд?
Автор просто тренируется и в коде много велосипедов которые можно убрать, там 80% занимает имплементация того что уже есть в std (std::function). А лямбда просто позволяет писать короче и ничего больше, например раньше было:
struct MyFunc
{
… // ручками сохраняем все окружение и прокидываем в конструктор
void operator()
{

}
};
another_func( MyFunc() );
то сейчас можно
another_func( ()[] {… } ); // окружение можно перечислить в [] и их прокинет компилятор за нас
грубо стало меньше печатанья для создания калбеков.
Плюс затащили std::function из буста чтобы можно было такие калбеки удобно сохранять и передавать.
Эти вещи и раньше на C++ делали, просто печатать приходилось гораздо больше, а теперь компилятор в этом деле помогает.
Sign up to leave a comment.

Articles