Как стать автором
Обновить

Комментарии 26

Я захотел собрать велосипед с первой картинки!
Удобно, чтобы для каждой из этих операций была своя очередь.

А в чём именно удобство? Мне это совершенно не очевидно.

Очевидное решение — реализация шаблона пулл потоков.

Нуу например std::async в реализации MS именно так и работает.
А в чём именно удобство? Мне это совершенно не очевидно
SRP — жеж. Ну и отсутствие возможности для dead-lock'а — если вся работа с одним ресурсом (файловой системой, к примеру) идет через одну очередь.
Нуу например std::async в реализации MS именно так и работает.
std::async — это тот еще костыль ИМХО. Получение значения оттуда через std::future полностью блокирует текущий поток (что для Main Thread не приемлемо в принципе). А я не хочу блокироваться, я хочу получить уведомление о том, что все закончилось в контексте текущей очереди тогда, когда данные будут получены.
SRP — жеж. Ну и отсутствие возможности для dead-lock'а — если вся работа с одним ресурсом (файловой системой, к примеру) идет через одну очередь.

Согласно SRP нам нужна отдельная сущность «пул потоков». Зачем нам надо много пулов потоков я пока так и не понял. Насчёт dead-lock'a… Во-первых это не так просто, т.к. ресурс у пула потоков наверняка не один (как минимум по числу аппаратных потоков). Ну и если всё же есть его вероятность (т.е. имеем много спящих потоков), то зачем тогда вообще использовать пул, а не просто отдельные потоки на каждую задачу?
std::async — это тот еще костыль ИМХО. Получение значения оттуда через std::future полностью блокирует текущий поток (что для Main Thread не приемлемо в принципе). А я не хочу блокироваться, я хочу получить уведомление о том, что все закончилось в контексте текущей очереди тогда, когда данные будут получены.

Ничего подобного. При использование std::async совершенно не обязательно блокировать вызывающий поток. Блокирование — это всего лишь один из сценариев. Можно и опрашивать, а можно и ждать уведомления. Кстати, как раз в той статье, на которую вы поместили ссылку в начале, были явно показаны все 3 варианта.
Зачем нам надо много пулов потоков я пока так и не понял
У нас только один пулл потоков. Он синглтон
Нууу хорошо, пусть один пул с несколькими очередями (хотя по сути это… ) в него. Всё равно не понятно зачем их несколько. Можно какие-то практические примеры? )

P.S. Как вы понимаете у меня вопросы/претензии не к вашему велосипедику, а к изначальной схеме…
Хорошо. Представьте, что вам нужно вычитать N файлов и распарсить их. В другом участке кода вам нужно послать запрос по сети (пусть синхронно, не суть), а еще вам нужно сгенерировать M изображений в фоне (или вычитать их из сети), а потом изобразить их в главном потоке. Потом… тут подставьте еще много полезных, нужных вещей, которые так нужны современным приложениям.
Вы можете создать по потоку для каждого типа операций. И устроить очередь в этом отдельном потоке. При уничтожении этой очереди удаляем поток.
Но создание/удаление потока довольно дорогостоящая операция. Проще держать уже готовый поток, который будет обрабатывать задания из многих очередей. В этом и есть смысл пула потоков. У нас может быть одновременно очень много очередей, Добавление/удаление очередей и добавление заданий в очередь очень дешевая операция. Задания выполняются согласно приоритетам.
Можете воспринимать очереди как очень легковесные потоки
Вы похоже не понимаете что я спрашиваю. Я не спрашиваю зачем нам многопоточность или зачем нужны пулы потоков в принципе. Мне интересно почему мы не можем написать просто так (в вашей терминологии):
async_queue.async([=]{
    file_data = get_data_from_file( file_name );
    async_queue.async([=]{
        parsed_data = parse( file_data );
        main_queue.async([=]{
            update_ui_with_new_data( parsed_data ) ;
        });
    });
});

или вообще так:
async_queue.async([=]{
    file_data = get_data_from_file( file_name );
    parsed_data = parse( file_data );
    main_queue.async([=]{
        update_ui_with_new_data( parsed_data ) ;
    });
});

Т.е. зачем более одной очереди (отправку исполнения в UI поток рассматриваем как отдельный особый случай).

Ну и безотносительно всего этого… Создание/удаление потока имеют обычно ничтожные затраты на фоне самой задачи (уж точно на фоне перечисленных вами в последнем комментарии задач). И проблема у схемы «по потоку на задачу» совсем не с этим. А с тем, что если наплодить много активных потоков, то эффективность резко уменьшится за счёт накладных расходов на переключение контекста (вот это становится действительно затратная операция с учётом её частоты).

Да, и у вас совсем не легковесные потоки (которые coroutine или fiber), а именно пул потоков с очередями задач, Т.к. для легковесных потоков вам потребовался бы ещё какой-то механизм многозадачности (кооперативной как минимум, т.е. что-то типа yield).
Возможно не совсем хороший пример. Мы можем написать как угодно. Разные очереди полезны для абстрагирования от подсистем. Например отделить UI в Main Tread от сетевой подсистемы. Сетевую подсистему от системы записи/чтения с диска, и все предыдущие системы от подсистемы обработки изображений (к примеру).
Всё равно непонятно что за абстрагирование такое, если пул потоков и так уже выделен в отдельную абстракцию. В общем ни одного примера показывающего хотя бы минимальную полезность нескольких очередей (кроме случая с main ui, который является особым) я так не увидел.

Ну и соответственно если остановится на том, что нам не требуется много очередей, то по сути окажется что практически всё уже реализована в стандартной библиотеке.

Не реализованным остался только механизм обработки результатов выполнения дополнительных потоков в основном потоке (UI). В смысле в стандарте C++11 этого нет. В C++14 уже планируют добавить, в Boost'e уже сейчас есть. Ну и естественно можно плодить множество своих велосипедиков, как во всех этих последних статьях.
в objective-c очереди хороши тем, что можно манипулировать очерёдностью выполнения блоков в этой очереди.
Скажем, есть очередь для парсинга файлов, в ней ожидают своей очереди на обработку несколько блоков парсинга. те, что не выполняются можно своевременно отменить или поставить в начало очереди.
Это полезно, если хочется выставлять каким то задачам более высокие приоритеты (например, общение с основным потоком).
Плюс отделение задач в разные очереди полезны хотя бы для логического разделения. Тут сеть, тут IO, тут UI
Пришел узнать, как распараллелить наибольший общий делитель (greatest common divisor) :)
Как же все-таки обычный Objective C элегантен.

При всем уважении, надеюсь, что ваш велосипед найдет достойное место среди музейных экспонатов, и я никогда не встречу его в реальных проектах.
Ну, если говорить о Grand Central Dispatch, то он имеет C интерфейс, и оперирует блоками, аналогами которых есть лямбды в C++. Практически ничего от obj-c (кроме блоков) в GCD нет.
Сравните
obj-c:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // do smth
    dispatch_async(dispatch_get_main_queue(), ^{
        // do smth in main thread
    });
});

C++:
dispatch::queue(dispatch::QUEUE_PRIORITY::DEFAULT).async([]{
    // do smth
    dispatch::queue::main_queue()->async([]{
        // do smth in mai thread
    });
});


Для вас замена "[]" на "^" при объявлении замыкания настолько критична?
Да, первый выглядит гораздо понятнее. Алсо, обычно я не использую стандартные очереди:

    static dispatch_once_t once;
    static dispatch_queue_t queue;
    
    dispatch_once(&once, ^{
        queue = dispatch_queue_create("queue name", DISPATCH_QUEUE_SERIAL);
    });

    dispatch_async(queue, ^{
        // do smth
        dispatch_async(dispatch_get_main_queue(), ^{
             // do smth in main thread
        });
    });


блоки сами по себе не совсем чистый си, а полноценный objc объект. ^{} — это синтаксический сахар.

Стандартный блок можно засовывать, например, в NSDictionary, NSArray. А с вашими блоками возникнет проблема. Да можно засовывать в плюсовые структуры. Но я не хочу париться с этим, когда 70% кода проекта реюзается из собственных библиотек objc, выполненных в предыдущих проектах. Последнее, пожалуй, основная причина, почему я не люблю плюсовые велосипеды.

На последних трёх проектах, которые пострадали от такого велосипедостроения стало понятно то, что поддерживать такое может только велосипедостроители, причем те самые, что это написали (не другие). Если они не доступны, проект быстрее переписать, чем другому разработчику тратить время на вхождение в код.

А поскольку программирование — это нечто отличное, чем африканский труд, стараюсь не связываться с плюсовиками в iOS/OSX разработке.

Сорри, это немного наболевшее.
Если вы пишете только под iOS/OSX и используете, clang с эпловой стандартной библиотекой с++11, то вы можете и лямбды и блоки хранить в NS-контейнерах:
NSArray* array = @[ []{ std::cout << "hello" << std::endl; } ];
void(^say_hello)() = [array lastObject];
say_hello();
А теперь представьте, что вы пишете кроссплатформенный код на чистом С++. Например вы используете cocos2d-x, и хотите чтобы ваш код запускался на iOS, Android, WindowsPhone8, BlackBerry, MeeGo, win32, Linux, Win8 Metro, Mac OS X. Вы там не сможете использовать Objective-C блоки в принципе. Вот тут и появляются велосипеды ))
Не бойтесь велосипедов,, пропеллерами разящих.
> Проверялся на clang++ 5.0

Простите, что?! Текущая версия clang'а — 3.3.
Простите, был взволнован.
>clang++ --version
Apple LLVM version 5.0 (clang-500.1.61) (based on LLVM 3.3svn)
Target: x86_64-apple-darwin12.4.0
Thread model: posix
Ну… так не 5.0, а 3.3. Либо же стоило указать, что Apple Clang использовался.
Уже поправил
file_io_queue.async([=]{
    file_data = get_data_from_file( file_name );
    parser_queue.async([=]{
        parsed_data = parse( file_data );
        main_queue.async([=]{
            update_ui_with_new_data( parsed_data ) ;
        });
    });
});


Если абстрагироваться от велосипедов и вернуться к реальной разработке, то с помощью Boost.Asio вы можете делать именно то что вы в этом примере привели (почти буквально). Только вместо слова async будет слово post, а вместо queue будет io_service.
Ну да. Но на то он и велосипед, чтобы попытаться самому реализовать уже реализованное.
Просто фраза «Но почти все реализации, виденные мной на просторах интернетиков, предлагают использовать один std::thread для одной очереди. С моей точки зрения это не есть хорошо. » — она предполагает что были исследованы основные реализации.
А на деле оказывается что самая распространенная, Boost.Asio, являющаяся практически стандартом дефакто, вообще не рассмотрена. Не удивлюсь если окажется что вообще ничего из мейнстримных библиотек не было исследовано, и та фраза — просто фигура речи.
А можете набросать хотя бы в таком виде такой же пример на бусте? Я поглядел на их примеры, сходу не понятно как у них решается вот эта строчка:
auto main_queue = queue::main_queue();
Пока таращился на последнюю картинку, придумал еще один вариант велосипеда: примерно такая же конструкция, только колеса управляются отдельными педалями. Захотел повернуть — притормозил одно колесо. Как в танке )
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации