Comments 21
Есть возможность сравнить на вашем же железе с реализацией std::async от Microsoft? В MSVC async работает поверх Parallel Patterns Library и не порождает много лишних потоков.
+1
У меня получились другие результаты:
MSVC v12:
GCC 4.8.1
Параметры:
Судя по этим результатам, стандартная реализация всегда чуть лучше.
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
Судя по этим результатам, стандартная реализация всегда чуть лучше.
+1
На моей машине оптимальным значением N_THREADS было количество на единицу меньше, чем процессоров. При большем количестве — стандартная реализация быстрее.
+1
Все равно стандартная реализация быстрее:
MSVC v12:
GCC 4.8.1:
Параметры:
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
0
Ошибки:
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 висит до посинения, пока не придет задача. И не пришлось бы городить такой сложный огород.
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 висит до посинения, пока не придет задача. И не пришлось бы городить такой сложный огород.
+5
А я не понял, в каком месте там sigsegv при захвате? Неявный захват this?
Автор, все же оно называется лямбда-выражение, а разворачивается в замыкание.
Автор, все же оно называется лямбда-выражение, а разворачивается в замыкание.
+1
При создании задачи.
SIGSEGV При вызове t->invoke();
Если передавать строки, то там мусор. Хотя указатель правильный.
Но так работает.
Не было времени продебажить этот момент.
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));
Не было времени продебажить этот момент.
0
1. Логично что, лучше запускать async «пачками» по количеству процессоров. Я хотел сделать свою библиотеку, чтобы не придавать этому значение.
2. Напоминаю, что это just 4 fun проект. Я пользовался знакомыми мне сущностями. Я не писал до этого на C++11.
3. Я и не против std::future. Все уже написано до меня, но я сделал это вопреки.
4. Огород не сложный, он просто не изящный.
Спасибо за замечания, я писал статью именно для этого.
2. Напоминаю, что это just 4 fun проект. Я пользовался знакомыми мне сущностями. Я не писал до этого на C++11.
3. Я и не против std::future. Все уже написано до меня, но я сделал это вопреки.
4. Огород не сложный, он просто не изящный.
Спасибо за замечания, я писал статью именно для этого.
0
У вас проблема в том, что вы берете ссылки как есть, а они у вас дохнут еще до начала выполнения задачи, вот вам и SIGSEGV. Используя же std::decay вы убережете себя от этого. Так же и правильно отработают rvalue, значения которых 100% не доживёт до начала выполнения задачи.
В вашем случае что будет, если сигнатура функции будет содержать const std::string& и вы прокинете строку? Естественно SIGSEGV, ибо объект строки сдохнет после добавления в очередь, а вот на этапе выполнения ссылка уже будет мертва.
Так что изучать всё же стоит сразу нормально, а биться головой о стену, в надежде, что в нужный момент ваш код не упадёт.
В вашем случае что будет, если сигнатура функции будет содержать const std::string& и вы прокинете строку? Естественно SIGSEGV, ибо объект строки сдохнет после добавления в очередь, а вот на этапе выполнения ссылка уже будет мертва.
Так что изучать всё же стоит сразу нормально, а биться головой о стену, в надежде, что в нужный момент ваш код не упадёт.
0
По поводу этого:
Зачем использовать
Или я что-то не понял?
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())>
{
};
Или я что-то не понял?
0
Главный цикл дочернего потока контролируется атомарной переменной (по идее достаточно было объявить ее volatile, так как тут нет состояния гонки, главный поток в нее только пишет, а дочерние только читают)
volatile в C++ никак не связан с многопоточностью и атомарностью.
Состояние гонки всегда есть, если в общие данные пишет хотя бы один из потоков, когда другие их читают или пишут. Помимо этого, стандартные атомики (не relaxed) влекут за собой acquire-release семантику и устраняют memory reordering, так что они не только ради атомарности нужны.
volatile в C++ никак не связан с многопоточностью и атомарностью.
Состояние гонки всегда есть, если в общие данные пишет хотя бы один из потоков, когда другие их читают или пишут. Помимо этого, стандартные атомики (не relaxed) влекут за собой acquire-release семантику и устраняют memory reordering, так что они не только ради атомарности нужны.
+1
особой связи с мульти-тредингом я как-то в коде не вижу — наверно около 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» лутше изучать отдельно, там выше крыше своих хитростей.
оставшееся 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
Интересно, а есть люди, которые используют для этой цели Grand Central Dispatch не на OSX или iOS?
+1
Примерно с появлением С++11 я ушел из мира С++ в .net (так получилось, производственная необходимость). Холиварить о языках не хочу, у меня просто вопрос. Сколько смотрю на реализацию лямбд, асинхронности и прочих плюшек в С++, не могу не отметить, что все это несопоставимо более громоздко выглядит, чем в C#. Вопрос к тем, кто имел возможность поработать и с тем, и с тем: в С++ оно действительно нужно? Может быть у этого языка должен быть просто другой путь развития? Или всем ОО-языкам предстоит пройти одни и те же ступени (например лямбды есть в Python и Java 8)? Есть хотя бы в теории альтернатива лямбд?
-1
Автор просто тренируется и в коде много велосипедов которые можно убрать, там 80% занимает имплементация того что уже есть в std (std::function). А лямбда просто позволяет писать короче и ничего больше, например раньше было:
struct MyFunc
{
… // ручками сохраняем все окружение и прокидываем в конструктор
void operator()
{
…
}
};
another_func( MyFunc() );
то сейчас можно
another_func( ()[] {… } ); // окружение можно перечислить в [] и их прокинет компилятор за нас
грубо стало меньше печатанья для создания калбеков.
Плюс затащили std::function из буста чтобы можно было такие калбеки удобно сохранять и передавать.
Эти вещи и раньше на C++ делали, просто печатать приходилось гораздо больше, а теперь компилятор в этом деле помогает.
struct MyFunc
{
… // ручками сохраняем все окружение и прокидываем в конструктор
void operator()
{
…
}
};
another_func( MyFunc() );
то сейчас можно
another_func( ()[] {… } ); // окружение можно перечислить в [] и их прокинет компилятор за нас
грубо стало меньше печатанья для создания калбеков.
Плюс затащили std::function из буста чтобы можно было такие калбеки удобно сохранять и передавать.
Эти вещи и раньше на C++ делали, просто печатать приходилось гораздо больше, а теперь компилятор в этом деле помогает.
0
Sign up to leave a comment.
Асинхронные задачи в С++11