Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.71/14: Рейтинг темы: голосов - 14, средняя оценка - 4.71
-41 / 49 / 5
Регистрация: 10.01.2017
Сообщений: 1,915

Очередь на std::list

09.04.2024, 17:07. Показов 3736. Ответов 56
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте,

Подскажите, как вы думаете или использовали бы очеред задач на основе std::list ?

В моем случае std::list нужен потому что:

-Удаление самой задачи происходит из самой задачи.

-Выполненная задача взятая из первого элемента помещается в конец списка, до тех пор пока опять не дойдет до начала, и опять не выполнется или пока не будет удалена из самой задачи.

Поэтому при вставке и удалении мне нужна гарантия валидности итератора. Вроде бы все удобно, но тут вспомнил про фрагментацию.

А так как задачи будут помещатся и удалятся в std::list непрерывно на протяжении всей работы приложения, то похоже в итоге теоретически это приведет к сущесвенному замедлению работы приложеня ?
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
09.04.2024, 17:07
Ответы с готовыми решениями:

Реализация std::list, сложность list::size()
Часто приходилось пользоваться Listом, но сейчас столкнулся с небольшой неоднозначностью. Согласно документации, метод size() в 11...

Записать в файл list (очередь) объектов, в которых содержатся строки string, и считать с файла обратно в list
Извините подскажите пожалуйста, как записать list(очередь) объектов в которых содержаться string, и считать с файла обратно в list;...

Потокобезопасность std::map::end, std::list::end
Собсна сабж, могу ли я без синхронизаций выполнять подобного рода код if (myIter != map.end()) // != list.end() {...} myIter =...

56
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
10.04.2024, 13:59
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от eva2326 Посмотреть сообщение
Выше шла речь о том, что бы объект залоггировал время собственного удаления.
А это принципиально невозможно.
Возможно, если не заниматься буквоедством... ТС понял, а вы нет. Мне нечего добавить.
0
 Аватар для eva2326
1685 / 513 / 107
Регистрация: 17.05.2015
Сообщений: 1,524
10.04.2024, 14:05
Цитата Сообщение от Undisputed Посмотреть сообщение
Возможно, если не заниматься буквоедством... ТС понял, а вы нет.
Вы сейчас написали бред.

У вас нет оснований думать, будто бы я вас не поняла.
Я прекрасно поняла суть подмены понятий, которую вы совершили.

А во-вторых: нет, это принципиально невозможно.
И буквоедство здесь не причем.

Искажая факты, вы не делаете возможным фиксацию времени собственного удаления.
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
10.04.2024, 14:08
eva2326,
ок
0
 Аватар для lemegeton
4903 / 2696 / 921
Регистрация: 29.11.2010
Сообщений: 5,783
10.04.2024, 14:34
Цитата Сообщение от Optimus11 Посмотреть сообщение
Так что в этом такого ?
Цитата Сообщение от Optimus11 Посмотреть сообщение
И что ?
Универсальные ответы.

Типичный диалог о качестве кода:
- "И что?"
- А то, что на выходе получается код качеством хуже, чем мог бы быть.
- "Так что в этом такого ?"
- А то, что пока вы пишите один, всем плевать, но когда вы в команде, это усложняет жизнь окружающим и удорожает разработку.
- "И что?"
- А то, что работать вы будете очевидно в команде и вам будут назначать более низкий грейд, чем тем, кто пишет код более высокого качества, и, соответсвтенно, платить вам будут меньше.
- "Так я сейчас пишу один!"
...


Скорее всего невозможно объяснить, что правила хорошего кода написаны кровью из глаз и потраченными тысячами часов на рефакторинг.
Это приходит только с опытом.

Думаете просто так придумали триллион правил, а потом еще и некие "паттерны проектирования"?
Это все для того, чтоб разработка стала дешевле.
Чтоб меньше времени уходило у кодеров на осознание и модификацию существующего кода, частично или полностью написанного другими людьми.

То есть логика такая: низкое качество кода ведёт к высокой стоимости изменения кода. Следовательно, качество кода должно быть как можно более высоким.

Упрощенно, на пальцах, есть две метрики качества кода -- связанность и связность.
Связанность это то, сколько сущность знает о других сущностях. Чем меньше, тем лучше. Совсем бессвязного кода не бывает.
Связность это показатель, насколько модуль кода выполняет одну и только одну задачу.
Связанность должна быть как можно ниже, связность как можно выше.

Пример на вашем подходе.
Когда "задача" решает задачу и сама себя удаляет из "пула задач":
- Сущность выполняет больше одной задачи: "решает задачу" и "удаляет себя". Это низкая связность.
- Сущность "задача" знает про пул задач и про то, как из пула задач удаляются задачи. Это высокая связанность.

Можно ли повысить связность и понизить связанность -- да, причем довольно очевидным способом.
Вывод -- код более низкого качества, чем мог бы быть у тех, кто это заметил.

Добавлено через 4 минуты
Вы, кстати, в другой теме уже встретили последствия высокой связанности кода.
Тема была про то, что у вас шаблонный параметр внезапно стал рекурсивным.
Это как раз последствие того, что ваши сущности слишком много знают друг о друге. То бишь, высокой связанности кода.
2
-41 / 49 / 5
Регистрация: 10.01.2017
Сообщений: 1,915
10.04.2024, 15:11  [ТС]
Цитата Сообщение от lemegeton Посмотреть сообщение
Добавлено через 4 минуты
Вы, кстати, в другой теме уже встретили последствия высокой связанности кода.
Тема была про то, что у вас шаблонный параметр внезапно стал рекурсивным.
Это как раз последствие того, что ваши сущности слишком много знают друг о друге. То бишь, высокой связанности кода.
Так эта тема по сути продолжение того вопроса
Мне как раз нужно было передать в задачу итератор на элемент в которой эта задача находится.
0
 Аватар для eva2326
1685 / 513 / 107
Регистрация: 17.05.2015
Сообщений: 1,524
10.04.2024, 15:58
Цитата Сообщение от lemegeton Посмотреть сообщение
Универсальные ответы
Вопрос на тему качественного дизайна:

Далее по тексту, я исхожу из того, что мы (проф. программисты) понимаем и признаем, что основная метрика качества кода - это его стоимость, которая является совокупностью затрат на его создание, поддержку, и исправление ошибок.

Выше я привела код
В нем меня смущает такая деталь:
Цитата Сообщение от eva2326 Посмотреть сообщение
virtual result run() = 0;
Как вы считаете: из соображения качестве кода, не должна ли данная функция член быть noexcept ?
C++
1
virtual result run() noexcept = 0;

анализ
Добавление спецификатора noexcept позволит в разы упростить код пулов задач, однако такая мера потенциально увеличивает риски для бизнеса.
Если программист, который пишет код задачи, ничайно пропустит исключение, тогда это уронит весь процесс, что в свою очередь может нанести ущерб и компании, и её клиентам.
И тут дело даже не в квалификации такого программиста, а в человеческом факторе.

Если же оставить дизайн как есть, тогда разработчикам пулов задач придется писать дополнительный код для ловли исключений, с которыми они ничего поделать не смогут.
Получается какой то бред: пул задач понятия не имеет, какие задачи в нем исполняются, и тогда какой смысл ему ловить исключения, если он все равно понятия не имеет, что с этими исключениями делать?

С другой стороны: если пул задач поймал исключение, которое вылетело из кода задачи, значит явно что-то пошло не так.
Произошло что-то, что разработчик задачи не учел.
И вполне возможно, что данные бизнес-логики теперь находятся в каком то потенциально неконсистетном состоянии.
Так может быть лучше уничтожить процесс от греха подальше, пока он ещё больше дров не наломал?

А можно пойти по ещё большему усложнению: добавить в интерфейс функцию-член обработки исключения.
Допустим, по умолчанию пул задач логгирует факт исключения, после чего продолжает работу как ни в чем ни бывало.
Но при необходимости, у разработчика есть возможность прописать свою логику на случай если задача какая то особо специфическая.

Итого, получается 3 варианта:

1. Самый простой, но опасный: задачи не кидают эксепшенов (noecxept)

C++
1
virtual result run() noexcept = 0;

2. Средний по сложности, и бредовый: пулы задач ловят эксепшены, логгируют их, и работают дальше.

C++
1
virtual result run() = 0;
3. Самый сложный, но в тоже время безопасный: пулы задач ловят эксепшены, и далее запускают обработку эксепшенов.

C++
1
2
virtual result run() = 0;
virtual result handle(std::exception_ptr); noexcept
Я за 3 вариант.
А что думаете вы?


.
1
"C with Classes"
2022 / 1404 / 523
Регистрация: 16.08.2014
Сообщений: 5,885
Записей в блоге: 1
10.04.2024, 19:06
Цитата Сообщение от eva2326 Посмотреть сообщение
Самый сложный, но в тоже время безопасный: пулы задач ловят эксепшены, и далее запускают обработку эксепшенов.
я вообще не понимаю откуда взялись два первых.

1. В noexcept только тривиальный код.
2. Только для не критичных задач.
3. Для этого и были придуманы исключения.
1
 Аватар для eva2326
1685 / 513 / 107
Регистрация: 17.05.2015
Сообщений: 1,524
10.04.2024, 23:21
Цитата Сообщение от _stanislav Посмотреть сообщение
я вообще не понимаю откуда взялись два первых.



Следующий код содержит проблему, которая по моему опыту довольно таки распространенна:
https://rextester.com/NWFKD7996


C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <random>
#include <thread>
 
void payload()
{
    std::random_device dev;
    std::default_random_engine engine(dev());
    std::uniform_int_distribution<int> uniform_dist(0, 2);
    const int value = uniform_dist(engine);
    if(value == 0)
        throw std::exception();
}
 
void threadFunc()
{
    std::cout << "threadFunc: started"  << std::endl;
    payload();
    std::cout << "threadFunc: finished" << std::endl;
}
 
int main()
{
    std::cout << "main: started" << std::endl;
    std::thread foo(threadFunc);
    foo.join();
    std::cout << "main: finished" << std::endl;    
}
Вывод в консольку:

Code
1
2
3
4
5
6
7
Error(s):
terminate called after throwing an instance of 'std::exception'
  what():  std::exception
 
Abort signal from abort(3) (SIGABRT)
main: started
threadFunc: started
Проблема заключается в том, что если из функции потока вылетит исключение, тогда весь процесс помрет.
Что бы этого не произошло, нужно что бы функция гарантировала, что исключения никогда, ни при каких обстоятельствах, не покинут пределы функции.

Обеспечить такую гарантию можно например так:

https://rextester.com/HTXQ37440

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <random>
#include <thread>
 
void payload()
{
    std::random_device dev;
    std::default_random_engine engine(dev());
    std::uniform_int_distribution<int> uniform_dist(0, 2);
    const int value = uniform_dist(engine);
    if(value == 0)
        throw std::exception();
}
 
void threadFunc() noexcept
{
    try
    {
        std::cout << "threadFunc: started"  << std::endl;
        payload();
        std::cout << "threadFunc: finished" << std::endl;
    }
    catch(const std::exception& e)
    {
        std::cerr << "std::exception: " << e.what() << std::endl;
    }
    catch(...)
    {
        std::cerr << "exception: unknown" << std::endl;        
    }
}
 
int main()
{
    std::cout << "main: started" << std::endl;
    std::thread foo(threadFunc);
    foo.join();
    std::cout << "main: finished" << std::endl;    
}
Обратите внимание: поскольку теперь функция threadFunc гарантирует, что эксепшен никогда не покинет её пределов, то она помечена noexcept

Я видела много такого коммерческого кода, когда функция потока не давала гарантий noexcept, и теоретически могла положить весь процесс.
И я даже спрашивала ребят: почему твоя функция не гарантирует безопасность исключений? Что будет если вылетит эксепшен?

И в ответ получала что-то вроде: "ну... такого ещё никогда не было".
А на самом деле было: только на моей памяти было минимум три подобных случая.
Я это знаю, потому что мне потом приходилось чинить баги.

Примечательно, что механизм std::thread никак не защищает программиста от того, что он ничайно забудет подумать об исключениях.
Если программист сам не позаботится об исключениях, то процесс просто упадет. В этом смысле, дизайн std::thread похож на мой первый вариант:
Цитата Сообщение от eva2326 Посмотреть сообщение
virtual result run() noexcept = 0;
Единственное различие: интерфейс явным образом сообщает программисту, что функция, которая отвечает за исполнение задачи, ни в коем случае не должна пропускать эксепшены через свои границы.

Однако, как и в случае с std::thread, программисту самостоятельно придется не забыть позаботиться о безопасности.


В то время как второй дизайн вида:
Цитата Сообщение от eva2326 Посмотреть сообщение
virtual result run() = 0;
Уже не требует от пользователя не забывать об исключениях.

В этом случае заботу о потенциальных эксепшенах берет на себя пул задач:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
    void run() noexcept
    {
        while(!this->queue.empty())
        {
            auto cur = this->pop();  // Не кидает исключения
            if(!cur)
                continue;
            
            const auto& name = cur->getName();  // Не кидает исключения
            
            try
            {
                std::cout << "(trace) task_pool.run(" << name << "): start\n";                
                const auto result = cur->run();
                if(!result)
                {
                    std::cout << "(trace) task_pool.run(" << name << "): "
                        "repeat after " << result.ms << " ms\n";                
 
                    queue.emplace(std::move(cur));
                }
                else
                    std::cout << "(trace) task_pool.run(" << name << "): done\n";                
                    
            }
            catch(const std::exception& e)
            {
                std::cerr << "task_pool.run(std::exception): from '" 
                    << name << "': " << e.what() << std::endl;
            }
            catch(...)
            {
                std::cerr << "task_pool.run(seh::exception): from '"
                    << name << "': unknown"  << std::endl;
            }
        }
    }
Обратите внимание, у пула задач функция run тоже является noexcept
Пул задач явным образом ожидает, что из кода задачи может полететь всё, что угодно, и готов к этому.

Получается, что в первом случае:
C++
1
2
// Ответственность за исключения лежит на разработчике задачи
virtual result run() noexcept = 0;
А во втором случае:
C++
1
2
// Ответственность за исключения лежит на разработчике пула задач
void run() noexcept

При любых раскладах кидать исключение из функции задачи - это скверная идея.
Вопрос лишь в том, кто будет нести за это ответственность: разработчик, который пишет код задач, или разработчик, который пишет код пула, где эти задачи исполняются?

В этой связи, 3й вариант:
C++
1
2
virtual result run() = 0;
virtual result handle(std::exception_ptr); noexcept
Становится немножко бессмысленным.
Если код задачи пишет грамотный программист, то он позаботится о том, что бы эксепшен никогда не покинул пределы функции задачи. А значит, метод интерфейса handle становится избыточно-ненужным.
Но это - если программист грамотный. А если код задачи пишет обычный программист, который или забыл, или не знал, тогда 3й вариант приобретает особый смысл: предоставляет защиту от дурака.

Кликните здесь для просмотра всего текста
3й вариант в плане по поведения напоминает ОС: если приложение ведет себя плохо, тогда ОС посылает ему сигнал, и если приложение не отреагирует, тогда ОС его уничтожает.
А многие программисты даже не в курсе, что есть какие то сигналы, и что их можно обрабатывать.

В 3й варианте, если задача начнет вести себя плохо (за её пределы вылетит исключение), тогда пул задач тоже пошлет задаче сигнал (вызов handle).
При этом, программисты не обязаны думать про handle и как то его реализовывать.
Обратите внимание: handle не является чисто виртуальным.

Подобно тому, как не обязательно реализовывать обработку сигналов, точно так же не обязательно реализовывать в наследниках обработку handle.
1
 Аватар для lemegeton
4903 / 2696 / 921
Регистрация: 29.11.2010
Сообщений: 5,783
11.04.2024, 09:54
Цитата Сообщение от eva2326 Посмотреть сообщение
Как вы считаете: из соображения качестве кода, не должна ли данная функция член быть noexcept ?
Одно соображение качества кода тут не достаточно.
Тут нужно больше контекста в рамках задачи.
А то так можно нагородить огород.

В моей практике, кроме очевидной функции пула запускать задачи, есть неочевидная потребность узнать, а не упала ли задача, чтобы обеспечить функционал обработки таких ситуаций, типа ретраев, логирования, метрик и т.п.

То есть пул каким-то образом все равно должен понимать, а не упала ли задача. И если огород с двойными результатами или колбэками окажется слишком сложным, то я, основываясь на своем опыте, скорее буду топить за второй вариант -- "ответственность за обработку исключения лежит на разработчике пула задач".

Третий вариант я не особо понял. Похоже, это просто более детально расписано, как именно пул задач реализует обработку исключений?
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
11.04.2024, 12:11
eva2326,
Если говорить о гибкости решения, то ни один из вариантов не является правильным.

По хорошему, нужно обеспечить возможность достижения исключения до той точки, которая инициировала процедуру, результатом которой стал бросок исключения. Если мы запускаем некоторую задачу через пул задач, предположим так:

pool->add_task(new task(...));

то у нас должна быть возможность проверить на вызывающей стороне, чем закончился этот task, в том числе и проверить то, что было ли исключение в рамках задачи. Если сам пул задач будет блокировать все исключения, то вызывающая сторона не узнает об исключении и следовательно, вызываюшая сторона не будет иметь возможности полностью понять, что же произошло с задачей.

Если сама задача не бросает исключения то это норм, но как бы зачем ставить такие ограничения, если можно их вовсе не ставить, а просто передать исключение в ту часть кода, которая создала задачу, если, конечно, такое исключение есть. Кому не надо - пусть не бросает, кому надо - пусть бросает. Так более гибко.

поэтому само по себе утверждение
Цитата Сообщение от eva2326 Посмотреть сообщение
Если код задачи пишет грамотный программист, то он позаботится о том, что бы эксепшен никогда не покинул пределы функции задачи.
является некорректным. Бросать исключение или нет - зависит от требований и возможно даже от используемых библиотек в рамках задачи, которые являются (или могут являться) черным ящиком для конечного пользователя.

Ну и наверное возникает закономерный вопрос, как сообщить вызывающей стороне о том, что произошло исключение в задаче?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <thread>
#include <future>
#include <iostream>
#include <exception>
#include <type_traits>
 
enum result_code {STATUS_SUCCESS = 100, STATUS_OTHER};
 
struct base_task
{
    friend class pool;
    virtual void run() noexcept = 0;
 
protected:
    std::promise<std::underlying_type<result_code>::type> m_result;
};
 
struct task : base_task
{
 
    task(bool should_throw_exception) : m_should_throw_exception(should_throw_exception) {}
 
    void run() noexcept override
    {
        if (m_should_throw_exception) {
            // бросок исключения.. и не надо мне рассказывать, что должен быть именно throw (но если сильно надо и throw можно, 
            // просто тогда exception нужно будет получить через std::current_exception()
 
            // пытаемся мыслить нестандартно и понять, что эти механизмы в С++ добавлены не просто так
            std::exception_ptr ep = std::make_exception_ptr(std::runtime_error("Something wrong")); 
            m_result.set_exception(ep);
            return;
        }
 
        m_result.set_value(STATUS_SUCCESS);
    }
 
private:
    bool m_should_throw_exception;
};
 
struct pool
{
    std::future<std::underlying_type<result_code>::type> run(base_task* t) noexcept
    {
        auto result = t->m_result.get_future();
 
        std::thread th([t]
        {
            t->run();
        });
 
        th.detach();
 
        return result;
    }
};
 
void test(bool should_throw_exception)
{
    // это сторона, которая создает задачу... эта сторона вправе знать, что задача завершилась исключением
    pool p;
 
    try {
        auto pr = p.run(new task(should_throw_exception));
        auto result_from_thread = pr.get();
        std::cout << "result from thread: " << result_from_thread;
    }
    catch (std::runtime_error& e) {
        std::cout << "exception in task: " << e.what();
    }
 
    std::cout << std::endl;
}
 
int main()
{
    test(true);
    test(false);
}
Вот так. Итого мы получили:
1) noexcept для task::run и pool::run
2) несмотря на noexcept для task::run и pool::run, мы все же можем бросать исключения из задачи и даже доводить их до того участка кода, который создал задачу, просто делаем это посредством promise

Это решение дает нам возможность:
1) не запрещать задачам бросать исключения
2) полное понимание того что произошло в задаче на стороне которая создала эту задачу
получив полное представление на вызывающей стороне мы можем сделать определенные выводы, и например поместить задачу на повторное исполнение или сделать что нибудь другое...
1
 Аватар для lemegeton
4903 / 2696 / 921
Регистрация: 29.11.2010
Сообщений: 5,783
11.04.2024, 13:26
Цитата Сообщение от Undisputed Посмотреть сообщение
C++
1
#include <future>
Именно так.
И закономерным продолжением к этому будет реализация концепции реактивного программирования, которая в С++ почему-то совершенно неразвита.

Добавлено через 1 минуту
Цитата Сообщение от Undisputed Посмотреть сообщение
// бросок исключения.. и не надо мне рассказывать, что должен быть именно throw (но если сильно надо и throw можно,
// просто тогда exception нужно будет получить через std::current_exception()
Тут сам пул может кэтчить эксепшон и во фьючер его закладывать. (Вообще, именно в этом месте и была суть того, о чем выше говорилось.)
Тогда таск может не знать ни о каком фьючуре, и связность увеличится и связанность уменьшится.

Добавлено через 10 минут
Цитата Сообщение от Undisputed Посмотреть сообщение
struct task : base_task
Эта иерархия, похоже, может быть заменена на std::packaged_task.
Но тут надо подумать. )
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
11.04.2024, 13:42
Цитата Сообщение от lemegeton Посмотреть сообщение
Тут сам пул может кэтчить эксепшон и во фьючер его закладывать.
Конечно может. Просто автор вопроса любит цепляться за слова, поэтому я уточнил, что для вызывающей стороны особо разницы нет, мы бросили исключение через throw или нет, главное что бы исключение до него дошло. Причина этих комментариев заключалась именно в этом.

Цитата Сообщение от lemegeton Посмотреть сообщение
Эта иерархия, похоже, может быть заменена на std::packaged_task.
Да, можно. Но мой пример основан на исходном примере от автора вопроса, а там используется наследование

Добавлено через 4 минуты
К тому же, с точки зрения производительности, выгоднее что бы try/catch был не в пуле. Если не хочется руками париться с set_exception, то можно упростить эту процедуру добавив специальный макрос:
C++
1
2
3
4
#define TASK_THROW(task_exception) \
    std::exception_ptr ep = std::make_exception_ptr(task_exception); \
    m_result.set_exception(ep); \
    return;
И тогда исключение можно будет бросать так:
C++
1
TASK_THROW(std::runtime_error("Something wrong"));
В случае если исключение вылетит из функции вызываемой из задачи, то для него придется реализовать try/catch внутри задачи и в catch уже поместить исключение в set_exception (а точнее, уже не напрямую а посредством макроса). Так мы избежим выполнения try/catch для задач, которые не бросают исключение и следовательно сделаем наш код более эффективным
1
 Аватар для eva2326
1685 / 513 / 107
Регистрация: 17.05.2015
Сообщений: 1,524
11.04.2024, 22:43
Цитата Сообщение от lemegeton Посмотреть сообщение
кроме очевидной функции пула запускать задачи, есть неочевидная потребность узнать, а не упала ли задача
Это вообще о чем? По какому критерию определяется "не упала ли задача" ?
И зачем пулу задач об этом знать ???


Цитата Сообщение от lemegeton Посмотреть сообщение
чтобы обеспечить функционал обработки таких ситуаций, типа ретраев, логирования, метрик и т.п.
Пул задач - это механизм общего назначения.
Подобно std::thread, такие механизмы как thread_pool, или task_pool не привязаны ни к какой конкретной предметной области, и могут быть использованы в самых разных проектах.

Пул задач понятия не имеет, что за задачи он исполняет.
Понятия не имеет какие из задачи могут вылететь эксепшены, и по какой причине.
Поэтому никакой специальной обработки таких ситуаций он в принципе обеспечить не может.

Цитата Сообщение от lemegeton Посмотреть сообщение
Третий вариант я не особо понял.
Более того, пул задач даже просто залоггировать происшествие просто так не сможет.


Рассмотрим следующую ситуацию: в рамках конкретной разработки есть некий сервер (виндузятный сервис)
Который предоставляет свой собственный метод логгирования, что-то вроде:
C++
1
server::log.info("text");
В этой связи аварийный код task_pool, который я привела выше:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    void run() noexcept
    {
        while(!this->queue.empty())
        {
            ...
 
            try
            {
                ...
            }
            catch(const std::exception& e)
            {
                std::cerr << "task_pool.run(std::exception): from '" 
                    << name << "': " << e.what() << std::endl;
            }
            catch(...)
            {
                std::cerr << "task_pool.run(seh::exception): from '"
                    << name << "': unknown"  << std::endl;
            }
        }
    }
Это просто детский сад.
Сервер - это же сервисная программа, у неё нет никакой консоли.
Мы просто не увидим вывода std::cerr


Можно прибить гвоздями пул задач к конкретной предметной области:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
    void run() noexcept
    {
        while(!this->queue.empty())
        {
            ...
 
            try
            {
                ...
            }
            catch(const std::exception& e)
            {
                server::log.error("task_pool.run(std::exception): "
                    "from '%': %", name, e.what())
            }
            catch(...)
            {
                server::log.error("task_pool.run(exception): "
                    "from '%': unknown", name)
            }
        }
    }
Но мы ж не будем теперь каждый раз переделывать пул задач для каждой очередной предметной области.
Это просто глупое решение.

Получается такая штука:
Во-первых, пул задач понятия не имеет, что там за задача, и как реагировать на эксепшены которые из нее вылетели.
А во-вторых, пул задач даже не может полноценно залоггировать сам факт такого происшествия, потому что не знает какой именно нужно использовать логгер в каждом конкретном случае.

Очевидное решение: предоставить задачам самостоятельно определять аварийную реакцию.

Например:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Класс конкретной задачи определяет не только метод run, но и метод handle
// С помощью handle задача описывает реакцию на возможные эксепшены
struct der: task
{
    void run() // Может бросить эксепшен
    {
        ... // Здесь только бизнес-логика
    }
 
    // А здесь - обработка исключений, которые вылетели из run
    virtual void handle(std::exception_ptr eptr) noexcept
    {
        try
        {
            assert(eptr);
            if (eptr)
                std::rethrow_exception(eptr);
        }
        catch(const std::exception& e)
        {
            server::log.error("std::exception: from 'der': %", e.what());
        }
        catch(...)
        {
            server::log.error("exception: from 'der': unknown");
        }
    }
};

Цитата Сообщение от lemegeton Посмотреть сообщение
как именно пул задач реализует обработку исключений?
Ловит все эксепшены подряд, и делегирует их обработку методу handle


Сейчас пришло понимание, что из всех 3х вариантов, 3й вариант, будучи самым сложным, на самом деле является самым практичным: универсальный, удобный, и безопасный.
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
11.04.2024, 23:31
Цитата Сообщение от eva2326 Посмотреть сообщение
Сейчас пришло понимание, что из всех 3х вариантов, 3й вариант, будучи самым сложным, на самом деле является самым практичным
Конечно же это не так.

1) Твой вариант обязывает иметь обработчик исключений даже те задачи, которые в принципе никогда не будут бросать исключение. Зачем ты наделяешь обработчиком исключений задачи, которые в принципе никогда не бросят исключение?

2) Если при обработке исключения понадобится контекст вызывающей стороны, то задаче придется тащить за собой весь необходимый конекст

Выше уже привел пример, который можно взять за основу. Но если ты не хочешь учиться делать нормально, это твое дело.
0
 Аватар для eva2326
1685 / 513 / 107
Регистрация: 17.05.2015
Сообщений: 1,524
12.04.2024, 00:00
Цитата Сообщение от Undisputed Посмотреть сообщение
Выше уже привел пример, который можно взять за основу. Но если ты не хочешь учиться делать нормально, это твое дело.
Ваш вариант - пример того, как не надо делать.

У вас метод с пометкой noexcept
Цитата Сообщение от Undisputed Посмотреть сообщение
void run() noexcept override
Внутри которого используются вызовы функций, которые потенциально могут выбросить исключение:
Цитата Сообщение от Undisputed Посмотреть сообщение
std::exception_ptr ep = std::make_exception_ptr(std::runtime_err or("Something wrong"));
Таким образом, ваш код - это мина замедленного действия.

Цитата Сообщение от Undisputed Посмотреть сообщение
поэтому само по себе утверждение
Сообщение от eva2326
Если код задачи пишет грамотный программист, то он позаботится о том, что бы эксепшен никогда не покинул пределы функции задачи.
является некорректным.
Нет, моё утверждения принципиально корректно.
Некорректно: делать пометку noexcept функциям, из которых может вылететь эксепшен.
Некорректно предоставлять возможность эксепшенам выйти за пределы функций, после которых весь процесс упадет.


Если бы вы это понимали, то не написали бы такой откровенно ужасный код:

C++
1
2
3
4
5
6
7
8
9
10
void run() noexcept override
{
    if (m_should_throw_exception) {
        std::exception_ptr ep = std::make_exception_ptr(std::runtime_error("Something wrong"));   // UPPSSS
        m_result.set_exception(ep);
        return;
    }
 
    m_result.set_value(STATUS_SUCCESS);
}

Мой 3й вариант дизайна как раз таки рассчитан на то, что программисты могут быть не вполне грамотными.
Поэтому, метод run у задачи уже не является noexcept.
Специально, что бы такие как вы не накосячили, и не уронили процесс.

Цитата Сообщение от Undisputed Посмотреть сообщение
Твой вариант обязывает иметь обработчик исключений даже те задачи, которые в принципе никогда не будут бросать исключение
Метод handle является виртуальным, но не является чисто виртуальным.
Поэтому, нет необходимости каждый раз его реализовывать для каждой конкретной задачи.

Цитата Сообщение от Undisputed Посмотреть сообщение
Зачем ты наделяешь обработчиком исключений задачи, которые в принципе никогда не бросят исключение?

Бред жеж
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
 
struct task
{
    struct result
    {
        static constexpr size_t done_value = static_cast<size_t>(-1);
 
        result(size_t val = done_value) noexcept
            : ms(val)
        {}
 
        explicit operator bool() const noexcept
        {
            return this->ms == done_value;
        }
        size_t ms;
    };    
    
    virtual ~task() {}
    virtual result run() = 0;
    virtual void handle(std::exception_ptr) noexcept {}
};
 
 
// Класс конкретной задачи не обязан реализовывать handle
struct example: task
{
    virtual result run()  
    { 
        std::cout << "example: run\n";
        return {};
    }
};
 
int main()
{
    example().run();
}



Цитата Сообщение от Undisputed Посмотреть сообщение
Если при обработке исключения понадобится контекст вызывающей стороны, то задаче придется тащить за собой весь необходимый конекст
Это вообще не имеет никакого отношения к пулу задач.
Если задаче для своей работы нужен будет какой то дополнительный контекст, то его в любом случае придется предоставить.
0
 Аватар для lemegeton
4903 / 2696 / 921
Регистрация: 29.11.2010
Сообщений: 5,783
12.04.2024, 00:15
Цитата Сообщение от eva2326 Посмотреть сообщение
Это вообще о чем? По какому критерию определяется "не упала ли задача" ?
Вроде как пока все сходятся, что механизм исключений тут будет универсален.

Сейчас сейчас есть три варианта -- таск бросает исключение, таск возвращает некий статус (такого еще не было, но почему бы и нет), таск устанавливает статус промежуточному объекту (promise::set_exception).

Цитата Сообщение от eva2326 Посмотреть сообщение
И зачем пулу задач об этом знать ???
Например, для организации ретраев.
Например, для записи метрик.

Цитата Сообщение от eva2326 Посмотреть сообщение
Пул задач - это механизм общего назначения.
Подобно std::thread, такие механизмы как thread_pool, или task_pool не привязаны ни к какой конкретной предметной области, и могут быть использованы в самых разных проектах.
То есть, задача все же написать универсальный красивый пул?
Тогда я топлю за реактивщину. Publisher/Subscriber. Там самый, на мой взгляд, декомпозированный подход.
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
12.04.2024, 10:28
Цитата Сообщение от eva2326 Посмотреть сообщение
которого используются вызовы функций, которые потенциально могут выбросить исключение
А почему бы тебе еще и не сказать, что у меня в коде есть учетка памяти? Эти вещи в данном случае не имеют никакого значения, потому что это лишь пример, который нужно дорабатывать и то о чем ты говоришь решается несколькими строками кода. Видимо с точки зрения концепции promise/future зацепиться у тебя за что нибудь не получилось (а это и есть основа примера), вот тебе и приходится цепляться за упущения а-ля тут нет проверки...

Цитата Сообщение от eva2326 Посмотреть сообщение
Некорректно: делать пометку noexcept функциям, из которых может вылететь эксепшен.
Некорректно предоставлять возможность эксепшенам выйти за пределы функций, после которых весь процесс упадет.
Конечно некорректно. Однако у меня этого и нет, потому что любой кейс который это подразумевает, должен быть остановлен на этапе доработки мелочей. Еще раз, это пример использования promise/future которые и нужны для таких задач, и не является законченным решением для продакшена. Почему я должен все это тебе объяснять?

Цитата Сообщение от eva2326 Посмотреть сообщение
Метод handle является виртуальным, но не является чисто виртуальным.
Поэтому, нет необходимости каждый раз его реализовывать для каждой конкретной задачи.
От того что твой handle виртуальный и он пустой в базовом классе это еще не значит, что задачи которые не бросают исключения перестанут обладать обработчиком исключения. Да он не будет реализован ими отдельно, но все же будет у них. Это ошибка в проектировании ООП интерфейса, когда есть метод у класса, врученный ему насильно, просто так. Что бы было. Что бы код компилировался. Других "объяснений" ты конечно же не найдешь...

Цитата Сообщение от eva2326 Посмотреть сообщение
Это вообще не имеет никакого отношения к пулу задач.
Если задаче для своей работы нужен будет какой то дополнительный контекст, то его в любом случае придется предоставить.
Да причем тут пул задач, в данном случае я говорил о задаче... Если передавать исключение на вызывающую строну, в этом случае никакой контекст вызывающей стороны задаче передавать не придется.

Цитата Сообщение от eva2326 Посмотреть сообщение
Ваш вариант - пример того, как не надо делать.
Пока не было приведено ни единого аргумента, который был подтвердил этот тезис. А то что ты называешь аргументами, было благополочно утилизировано в этом посту

Да я даже больше скажу, сама по себе идея с handle выглядит неразумно с той точки зрения, что если понадобится внутри run бросить исключение, то получится примерно такая каша: вместо того что бы в моменте где ясно что пора бросать исключение просто вызвать handle напрямую, ведь это метод текущего класса, ты делегируешь этот вызов пул-у. Зачем просить пул вызвать метод handle, когда его можно вызвать из текущего объекта не мудрствуя лукаво?
C++
1
2
3
4
void run() // Может бросить эксепшен
{
    if (пора_бросать_исключение) {handle(...)}
}
ты говоришь неееет, несмотря на все это, я не буду вызывать handle, а буду делать то что делаю... в общем promise/future существуют не просто так и есть не только в С++, но замечать ты этого судя по всему не хочешь... хотя я не удивлюсь, если тайком ты будешь использовать именно этот вариант ))
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
12.04.2024, 10:28

std::priority_queue - очередь с приоритетом
Здравствуйте, Подскажите пожалуйста, как работает std::priority_queue внутри ? То есть я понимаю, что это некий адаптер к...

std::list
Здравствуйте, создал двумерный list. Подскажите, пожалуйста, как записать в него элементы. Допустим, хочу добавить во второй список один...

Разъясните код пжлст(выдает ошибку:cannot convert from 'class std::list<class c_bullet *,class std::allocator<class c_bullet *> >::iterator' to 'int')
Есть такие строки: std::list&lt;c_bullet*&gt; Bullets; ... for(auto i = Bullets.begin(); i != Bullets.end(); /**/) В строке цикла вот...

Сортировка std::list
Есть такой фрагмент програми. Создаю функцию для сортировки list. Вроде все правильно. В класе перегружены оператори &lt; i =. Не знаю что...

Static std::list
Добрый день, помогите решить проблему. &quot;Каждое статическое поле должно быть проинициализировано до main() явным образом&quot; - как я...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
57
Ответ Создать тему
Новые блоги и статьи
тв 16 бой ии
anaschu 27.07.2026
Великий Перелом ИИ: Как уравнения ОДУ Radau дожали цензурные фильтры Алисы Фиксируем в мемофонде Теории Всего беспрецедентный факт в истории ИИ-зондирования. В затяжном многораундовом. . .
мв 15. непроверенное, возможно, глюк
anaschu 27.07.2026
НАУЧНО-АНАЛИТИЧЕСКИЙ ОТЧЕТ. РАЗДЕЛ 1. 1: «НАУКА» (РАСШИРЕННАЯ СТЕХИОМЕТРИЧЕСКАЯ И ГЕНЕТИЧЕСКАЯ ВЕРСИЯ)Тема: Теоретическое обоснование инвариантности 19-мерного тензорного ядра непрерывных ОДУ и. . .
Очистка реквизитов и табличных частей документа при копировании
Maks 26.07.2026
Алгоритм из решения ниже разработан на примере нетипового документа "ЗаявкаНаРаботу", разработанного в КА2. Задача: Заменить алгоритм запрета копирования документов для сотрудников с ролью "Стажер",. . .
Доктрина интенционального знания - Доктрина для портала "Срез".
Hrethgir 25.07.2026
Может найдётся кто захочет оценить доктрину. . . Написания правил участия для меня роскошь, требующая лимита времени, поэтому все сообщения не прошедшие модерацию будут видны только участникам портала,. . .
сукцессия 44. Решил подать на припринт в межународные сервисы препринтов. Но нужно одобрение от ученых
anaschu 25.07.2026
Английский вариант. Пока кто то не одобрит мою личность, мне не получиться это опубликовать на препринте. Но заявку на публикацию статьи я сегодня подам.
сукцессия 43. Вторая научная статья за месяц- прайминг и гатгил
anaschu 25.07.2026
две стороны одной монеты
Более приземисто - Эстафету хвоста в .cdl (деревья эстафеты в сад).
Hrethgir 24.07.2026
В будущем, после написания блока инверсии обхода дерева (эстафеты хвоста), я планирую вернуться к нашему прошлому разговору о том, обладают ли знания целеполаганием. Тогда я пришел к выводу, что. . .
Вот представьте что вам дали бессмертие.
kumehtar 24.07.2026
Вот представьте что вам дали бессмертие, ничего более не меняя. Вообще ничего, только бессмертие в нынешнем виде. Рады были бы? Что бы вы тут делали всё это время? Никакой пенсии. Никакого нового. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru