Помощь - Поиск - Пользователи - Календарь
Полная версия этой страницы: Система, управляемая событиями и SST(super-simple tasker)
Форум разработчиков электроники ELECTRONIX.ru > Сайт и форум > В помощь начинающему > Программирование
Страницы: 1, 2, 3, 4, 5
brag
Вот эта статья http://embeddedgurus.com/state-space/categ...en-programming/ и картинка с нее

меня заразила в свое время переходом с традиционной блокирующей модели к event-driven. По мере отвыкания от блокинга количество тредов стало гораздо меньше, иногда и вовсе один. Со временем появились active-objectы, тк одного треда было не достаточно. И так я плавно двигался от блокирующей модели к неблокирующей. А когда увидел, что блокировок вовсе не осталось, вспомнил, что когда-то читал про некий SST, которому не уделил должного внимания из за привычки к блокингу, и начал с ним эксперименты, что в итоге привело к полному отказу от традиционной RTOS. Таким образом я потерял тайм-кванты, которые и так ни разу не пригодились, разве что в качестве костылей, которых SST-стиль не требует вовсе. Но получил взамен очень много, главное - избавился от кучи багов, накладных расходов и кучи ручной работы.
Код по мере опыта ставал меньше и проще, горбатые конструкции, которые перекочевали из блокирующего стиля были заменены на красивые чисто асинхронные(пример горбатой и нормальной очереди я уже приводил в этой теме).
amaora
Попробую нарисовать задачу, словами:

1. Основная часть работы в устройстве делается на прерываниях с высоким приоритетом, с этом вопросов нет, здесь никакая ОС не нужна.
2. Вспомогательная задача обработки текстовых команд приходящих по USART или CAN. Она может вызывать множество разных функций, большинство из которых вызывает printf. Который далее вызывает отправку в драйвер интерфейса (USART или CAN). В этом месте можно заблокироваться, данных выводится много а памяти под буфер мало.
3. Вспомогательная задача низкого приоритета, чисто вычислительная и очень тяжелая (считается ~10 с). Ее не нужно успевать считать в заданное время, достаточно как можно скорее. Из-за нее не должна остановится обработка текстовых команд.

Вопрос, как нужно будет написать код функции printf при использовании SST?
brag
Задача отлично ложится на SST. Понадобится неблокирующий printf, о его реализации позже.
CODE

enum{
TASK2_PRIORITY = 2, // higher
TASK3_PRIORITY = 1, // lower
}

// Собственно задача 2.
class Task2{
public:
Task2();
void operator()(uint8_t ch){
parse(ch);
} // можно обрабатывать не только по одному байту, но и сразу все, что есть в очереди, это лишь пример

private:
// это функция обработки, реализуем ее позже
void parse(uint8_t ch);
};

// Определяем очередь uart
struct UartQueue:
// размер нашей очереди - 32 байта. Он зависит от скорости работы порта и времени отработки остальных прерываний.
// Зачастую 16-32 байта вполне достаточно, но можем увеличить, все равно отказавшись от RTOS у нас появилось много памяти
public UmFifo_spsc_pop<uint8_t,Task2,TASK2_PRIORITY,32>
{
UartQueue():
UmFifo_spsc_pop(Task2())
{}
};

// Создаем очередь
// Данные в эту очередь можно пихать прямо из прерывания UART, в том числе через DMA
UartQueue uart_queue;
// Готово, теперь при поступлении данных Uart(или CAN) в очередь автоматически запустится
// обработчик Task2::operator() для их обработки.


// Низкоприоритетная задача
// Допустим, мы ее запускаем, когда у нас есть данные для обработки. Работает она 10 секунд и выходит
void run_task3(const Data* data){
SST::postMessage([data](){
// тут делаем очень тяжелую работу

}, TASK3_PRIORITY);
}

С этим думаю все понятно. Сейчас расскажу про printf.

Теперь printf.
Признаюсь, у меня пока нет реализации последовательного printf, я просто накидываю аргументы в очередь, память под которую получил за счет отказа от RTOS, от многостековости sm.gif
Нам нужно превратить линейную запись в цепочку вложенных лямбд.
Тут хорошо бы применить Promise https://promisesaplus.com/ , но я еще не дорос до их нормальной реализации на C++.
Поэтому паттерн будет пока такой:
Код
class Printf{
    Printf();

    Printf& operator,(const PrintfArgument& arg); // этой штукой я заряжаю аргументы в очередь,
// но мы хотим супер-экономии памяти, по этому будем делать по-другому
private:
    bool first; // понадобится позже, для определения первый аргумен(формат) или последующие
};

#define printf(args...) (Printf(), ##args)

В итоге мы сможем писать обычные printf-ы, но с callback-ом в конце:
Код
printf("%d %X\n", 123, 456, [](){
    // все, принтф отработал, можем делать что-то другое
});

Осталось реализовать наш operator, PrintfArgument и конструктор Printf.
продолжение следует.
amaora
А где самое главное, что будет вместо блокирующего putc в функциях печати чисел и строк?

По верхнему уровню, где вызывается printf. Что делать, если уровень вложенности становится большим? Так будет если раньше в коде было вот такое:

Код
do_something();
wait_for_something();
printf("something ...");

do_something();
wait_for_something();
printf("something ...");

do_something();
wait_for_something();
printf("something ...");

...

brag
В принципе тут без очереди все равно не обойдется - все равно где-то надо хранить аргументы printf. В блокинге мы их храним на стеке, а в нон-блокинге нужно хранить где-то в другом месте. Памяти займет это все равно меньше, чем резервировать стек.
Только в очереди будем хранить не сами печатаемые символы, а указатели на строки, числа итд, в общем аргументы функции, их у нас не много.
Тут как-раз рулит Раст, он не даст нам закинуть указатель на стековую строку, например, но мы делаем на плюсах, поэтому будем обходится тем, что есть.
В принципе такая реализация у меня есть. Приведу в удобоваримый вид и покажу.
amaora
Ну допустим будем хранить аргументы printf. Проблемы языков C и C++ не рассматриваем.

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

Как-то стало ясно, ничего иного не ожидал, сам долго думал как обойтись без вытесняющей rtos.
brag
И так вот. У нас появилась еще одна задача - printf. Назначим ей приоритет Printf_TASK_PRIORITY=3, тк она отрабатывает очень быстро, но можно и 2.
Код
class PrintfTaskQueue : public TaskQueue{
public:
    PrintfTaskQueue(): TaskQueue(Printf_TASK_PRIORITY){}
};

PrintfTaskQueue printf_queue;

Printf::Printf(){
    printf_queue.enqueueTask([this](){
        first = true;
    });
}

Printf& Printf::operator,(const PrintfArgument& arg){
    printf_queue.enqueueTask(arg);
    return *this;
}

Далее нам останется описать сам PrintfArgument, этим сейчас и займемся.

Цитата(amaora @ Sep 14 2016, 17:33) *
Ну допустим будем хранить аргументы printf. Проблемы языков C и C++ не рассматриваем.

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

Как-то стало ясно, ничего иного не ожидал, сам долго думал как обойтись без вытесняющей rtos.

Все эти флоаты у РТОС хранятся на стеке, а у нас будут в очереди. Места займут меньше, чем в стеке, это естественно.

Блокинг:
Код
do_something();
wait_for_something();
printf("something ...");

do_something();
wait_for_something();
printf("something ...");

do_something();
wait_for_something();
printf("something ...");

Тут либо надо использовать Promise, который реализовать на плюсах не просто, либо просто callback hell(куча вложенних лямбд)
Код
do_something([this](){
    printf("something ...",[this](){
        do_something([this](){
            printf("something ...", [this](){
                do_something([this](){
                    printf("something ...", on_finish);
                });
            });
        });
      });
});

либо разделять на отдельные функции, а состояние хранить в классе(вместо стека) - из за слабости языка С++.
Я пока использую hell, или разделение цепей вложенных вызовов на фанкции, чтобы код не разростался вправо. Возможно в будущем удастся реализовать compile-time промисы.
Togda буде что-то типа этого:
Код
Promise(
    do_something_nonblocking()).then(
    printf_nonblocking("something...")).then(
    do_something_else_nonblocking()).then(finish);

Для удобной работы нужна компиляторная языковая поддержка, компиляторы только недавно начали двигаться в асинхронный стиль, новшества С++ 11 хоть как-то помогают работать, но это еще очень слабая поддержка. Возможно у Rust она будет мощнее https://github.com/alexcrichton/futures-rs .
В то время, как блокирующий стиль давно поддерживается всеми, в том числе и железом. Над асинком еще нужно попотеть, чтобы он стал удобным, но, как говорится, Москва тоже не сразу строилась. В динамических языках поддержка уже довольно мощная, а в статических пока очень слабая либо вообще отсутствует.
sigmaN
Цитата
В принципе тут без очереди все равно не обойдется - все равно где-то надо хранить аргументы printf. В блокинге мы их храним на стеке, а в нон-блокинге нужно хранить где-то в другом месте. Памяти займет это все равно меньше, чем резервировать стек.
Понеслааась. А я говорил, что у вас вместо стека очереди будут память занимать и вы здорово себе усложняете межзадачное взаимодействие. Но нет, тут еще оказалось, что принтф свой надо бы неблокирующий изобрести и огромная машина по передаче аргументов через очередь ему будет сопутствовать... Да такая хитрая, что аж возможностей языка не очень то хватает. Эх.

Подитожим немного. Sizeof объектов уже растет для сохранения состояния, это мы выяснили. Очереди памяти требуют это мы тоже выяснили. Изобретать надо всё своё, в том числе принтф. А из-за небольшого прикольчика с разным кол-вом и типами аргументов тут придется задуматься... Классно. Продолжаем. Попкорна еще много ))

Вам не кажется, что у вас дело дошло до того, что вы реализуете всё на SST только ради того, чтобы реализовать что-нибудь на SST?
AHTOXA
Цитата(sigmaN @ Sep 14 2016, 23:33) *
Понеслааась.

Зря вы так ополчились на brag-а. Он говорит весьма интересные вещи. Постарайтесь их вычленить из странных высказываний про JS и Rust:)
Что касаемо обсуждаемой задачи с printf - сравните 2 варианта:
  1. N задач висят в ожидании освобождения устройства печати
  2. N задач поочерёдно быстренько запулили свои сообщения в очередь и завершились.

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

Я почему этой темой заинтересовался. Просто стал обращать внимание, что у меня в проектах с использованием RTOS процессы используют в основном два сценария:
  1. подождал флага - быстро обработал
  2. нечто вроде Active Object (это я сейчас прочитал, что такая техника называется Active Object, до этого не знал, что она так называется).

Судя по описанию, такие проекты очень хорошо должны лечь на SST. Жду примера с мигалкой светодиодом, чтоб пощупатьsm.gif
sigmaN
Ну я давно говорил, что в реальном мире оба подхода живы и это не просто так.
Моя придирчивость это противовес фанатичному переписыванию всего и вся, как предлагает нам пропагандист SST )))
brag
sizeof растет, но памяти в целом надо меньше, раз в 8, когда потоков много. А когда парочка - раза в 2 меньше.
printf и так давно свой и не потому что SST, а потому что он типобезопасный, в отличии от стандартного сишного. Сначала у меня был блокирующий, а потом стал неблокирующий printf.

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

Главное, чтобы запросов на печать в секунду было меньше, чем успевает пропустить сам порт, в который печатаем. Но если не успеет - не беда, просто переполнится очередь и часть данных потеряется.
В блокинге все равно хуже - будут тормоза всей системы, тк все компоненты системы, которые хотят принтф-ить будет ожидать заваленный порт какой-то одной, хуже того, низкоприоритетной задачей. В SST такой инверсии приоритетов нет(см картинку выше) .

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

Цитата
Судя по описанию, такие проекты очень хорошо должны лечь на SST. Жду примера с мигалкой светодиодом, чтоб пощупатьsm.gif

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

Демо-проект будет. Какая платформа предпочтительнее? ПК или какой-то МК? Может демо-плата какая-то стандартная? Помогите определится..

Цитата
Моя придирчивость это противовес фанатичному переписыванию всего и вся, как предлагает нам пропагандист SST )))

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

Например я недавно функцию sqrt сам написал(извлечение квадратного корня), но она не простая, она шаблонная и понимает как обычные int, так и большие числа (big-integer), до 65536 бит длиной, в том числе и комплексные sm.gif И сделал это не ради того, чтобы сделать, а задолбался от использования кривых и глючных библиотек и постоянного контроля типов.
А рядом с ней есть еще специализировання для float/double, которая завернута на стандартную. Вызвал sqrt для любого типа и готово, да и работает моя реализация быстрее этих кривых сишных поделок, местами раза в 2-4.

Я много чего пишу своего ради возможностей, ради компиляторной проверки. Многое оборачиваю в плюсы, если есть что оборачивать. Иногда проще заново написать, чем возится.
Времени хоть и требует немного, зато потом оно возвращается в виде более быстрой разработки проектов.

Повторюсь еще раз - блокинг-код тут не работает, если в предполагаемой либе нет async-io, значит на sst она работать не будет. Тут чисто асинхронный стиль в духе NodeJS и др.
AlexandrY
Цитата(AHTOXA @ Sep 14 2016, 22:00) *
[*] N задач поочерёдно быстренько запулили свои сообщения в очередь и завершились.
Думаю, что второй вариант гораздо выигрышнее и по памяти и по быстродействию. К тому же есть отличные, типобезопасные реализации printf на плюсах, которые будет проще сделать неблокирующимися, чем штатный printf.


А не смущает ли то, что ради этой фишки с printf-ом вам надо все! задачи в своей системе сделать без циклов.
Т.е. все задачи должны вызываться как функции и возвращать управление за конечное время.

А известно ли уважаемому brag-у что у нас в отрасли встраиваемых систем многие пакеты идут в виде скомпилированных либ с уже реализованными там циклическими задачами.

Но я на 100% уверен что, brag не перепишет и обычные открытые стеки TCP или USB где всегда есть циклическая задача на прием.
Эт я еще не упоминаю циклические задачи серверов, сессий, всяких драйверов и т.д.

Что интересно, стек то не сильно экономится в этом АКОП-е.
Общий-то он общий, но за то надо думать как поместить в него все стеки задач и прерываний во всех мыслимых комбинациях.
Из-за этого будет сделан наверняка огромный запас, процентально больший чем запас в стеках обычных задач RTOS.

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

brag
Цитата
Но я на 100% уверен что, brag не перепишет и обычные открытые стеки TCP или USB где всегда есть циклическая задача на прием.
Эт я еще не упоминаю циклические задачи серверов, сессий, всяких драйверов и т.д.

Это написани давно, циклов там нет. Например в USB данные ловятся в обработчике прерываний без всяких ожиданий, и в конце стека(например CDC) отправляются в очередь пользователю.
Например вот функция чтения с CDC.
Код
uint32_t UsbCdc::read(uint32_t *data32, uint32_t len8, const delegate<void(int)>& rdXfrc){
  // must never happen
    if(!isReady())return Error::NOT_READY;
    if(isReadBusy())return Error::BUSY;

    this->rdXfrc = rdXfrc;

        // добавление задачи в очередь конкретного endpoint
//  По окончанию чтения(фактически это просто прерывание) будет вызвана rdXfrc
    Usb::getInst()->core()->epOutXfr(USB_CDC_EP_OUT,data32,len8, this);
    return 0;
}

Аналогичное и в коде MSC(mass storage) и других классах. Работает изумительно и без тормозов, на RTOS была производительность ниже из за накладных расходов на циклы и переключение контекста.
Код да, не публичный, но какие-то части его публиковать имею право.

Задач в сложном проекте не десяток, их там несколько десятков, а то и сотня.
Стек да, с одной стороны считается еще труднее, чем в RTOS, а с другой - время жизни задач очень короткое, вся рекурсия идет все равно через очереди.
Я как обычно делаю: Выбираю самую тяжелую по стеку задачу для каждого уровня приоритета (не вручную ессно, этим занимается софт), потом эти числа суммирую и накидываю сверху процентов 20 запаса.
Смысл в чем - если задача в данном приоритете начала работу - она не может быть прервана другой задачей этого же приоритета или более низкого. Получается, в худшем случаи, если все самые тяжелые задачи каждого приоритета вытеснят одна другую по цепочке - это наихудший случай, на практике маловероятен.

Цифры получаются гораздо меньше, чем в аналогичных проектах в блокинг-стиле, там все то, что в SST идет в очереди, в строго определенном формате, в RTOS идет в стеки в таком виде, который вздумается компилятору, и зачастую далеко не оптимальном, с копиями или лишними указателями/ссылками. Время жизни этих стеков велико и стек очень трудно просчитывается, особенно когда куча вложенных виртуальных блокирующих функций.

Потом ОЗУ заполняется данными сверху, а стеком снизу. Нет необходимости вообще что-то выделять. Если есть MPU - данные защищаю им, если стек туда долезет - будет глюк, но он залогируется MPU. РТОС по любому больше расходует памяти, тк у SST стек плотный, а у РТОС каждый стек имеет пустоты, и это без учета того, что есть еще TCB и всякие другие конструкции(те же семафоры тоже память занимают), которых в SST нет. В SST есть очереди, но и в РТОС-коде они тоже обычно есть. Без них - это полный ад.
AHTOXA
Цитата(brag @ Sep 15 2016, 00:42) *
Демо-проект будет. Какая платформа предпочтительнее? ПК или какой-то МК? Может демо-плата какая-то стандартная? Помогите определится..

Так вы же написали, что проект будет под PC. Думаю, что это наиболее приемлемый вариант для всех.

Цитата(AlexandrY @ Sep 15 2016, 01:11) *
А не смущает ли то, что ради этой фишки с printf-ом вам надо все! задачи в своей системе сделать без циклов.
Т.е. все задачи должны вызываться как функции и возвращать управление за конечное время.

Я ведь написал, что у меня сейчас практически так и есть.
Что касаемо либ без исходников, и прочих непреодолимых препятствий - наверное можно сделать гибридный проект. Несколько процессов останутся как есть, а те процессы, которые можно сделать неблокирующимися, свернём в один процесс, и их будет крутить SST.
(Но это будет временное решение, ненадолго, просто до того момента, когда brag перепишет все либы в неблокирующем стилеsm.gif))
brag
Цитата
Так вы же написали, что проект будет под PC. Думаю, что это наиболее приемлемый вариант для всех.

Ок, так и сделаем. Кто захочет - запросто под МК можно будет присобачить, без правки кода. Заглушки только поменять на работу с железом

Цитата
Несколько процессов останутся как есть, а те процессы, которые можно сделать неблокирующимися, свернём в один процесс, и их будет крутить SST.

Трудно совместить SST-планировщик с готовой РТОС, хотя, думаю можно(от камня сильно зависит и от самой РТОС). Но можно просто актив-object-ов наклепать - будет асинхронный стиль, как у SST, но можно будет пользоваться и блокинг-тредами - для либ, которые не поддерживают асинк. Цена - много памяти под стеки. Общаться между всем этим только через очереди - никаких shared-переменных и мютексов.

Цитата
(Но это будет временное решение, ненадолго, просто до того момента, когда brag перепишет все либы в неблокирующем стилеsm.gif))

И получит права на их публикацию sm.gif Да куча сейчас либ на самом деле асинхронных или просто независимых от РТОС/блокинга, хотя согласен, есть далеко не все.

Для кортекс вряд ли просто так присобачишь SST к РТОС. В виду его архитектуры пришлось использовать PendSV и SVC, да еще и с трюками. Обычно эти исключения уже заняты РТОС, а рекурсивные прерывания в Кортексе просто так не сделаешь.
Код
__attribute__((naked)) void ePendSV(){
    asm volatile(
        // "push {r0,r4-r11, lr} \n" // not necessary, callee saved
        // create new stack frame for scheduler
        "mov  r2, %0    \n" // PSR
        "movw r1, #:lower16:SST_sync_scheduler  \n" // PC
        "movt r1, #:upper16:SST_sync_scheduler  \n"
        "movw r0, #:lower16:async_scheduler_return  \n" // LR
        "movt r0, #:upper16:async_scheduler_return  \n"
        "push {r0, r1, r2} \n" // push {lr, pc, xPSR}
        "sub sp, #5*4       \n" // push {r0,r1,r2,r3,r12} - undefined values
        // return from exception to scheduler
        "bx lr    \n"
    : :"i"(xPSR_RESET_VALUE));
}

// return to preemtded task through SVC
extern "C" __attribute__((naked)) void async_scheduler_return(){
    asm volatile("svc #0");
}

__attribute__((naked)) void eSVCall(){
    asm volatile(
        // kill current stack frame
        "add sp, #8*4 \n"
        // "pop {r4-r11, lr} \n" // not necessary
        // perform an EXC_RETURN (unstacking)
        "bx lr \n"
    );
}
AlexandrY
Цитата(brag @ Sep 15 2016, 00:13) *
И получит права на их публикацию sm.gif Да куча сейчас либ на самом деле асинхронных или просто независимых от РТОС/блокинга, хотя согласен, есть далеко не все.


Это вы наверно имеете в виду либы от Microchip-а

Да, Microchip никогда не использовал RTOS, у него всегда все работало в суперцикле.
И наверняка есть тысячи разработчиков пишущих сейчас в том же стиле.
В конце концов все ардуинщики так делают, они же тоже программисты biggrin.gif

Но только не надо продвигать это как некий прогресс.
Со своим AKOП-ом перед RTOS вы в такой же ситуации как C-и перед C++

Сейчас по аналогии, уже уместно начать доказывать, что RTOS далеко не так медлительна как тут представляется.
Что там есть прерывания ядра, которые работают быстрее прерываний АКОП-а
И если уж делать гибрид RTOS с другим механизмом, то лучше чистый автоматный метод применять, а не АКОП.
Чистые автоматы можно хотя бы строго верифицировать в симулинке.
brag
не знаю что там у них за супер-цикл, лично у меня этот суперцикл выглядит следующим образом:
Код
while(true){
       sleep();
}

И он единственный во всем проекте. sleep - это обертка ассемблерной инструкции проца, если что, больше она ничего не делает.

Цитата
Со своим AKOП-ом перед RTOS вы в такой же ситуации как C-и перед C++

Глупо сравнивать одно с другим глипо в таком дуге. Тут скорее надо проводить аналогию Java и Javascript. Оба мощные языка и позволяют решать одни и те же задачи, но стиль программирования у них совершенно разный.

Цитата
И если уж делать гибрид RTOS с другим механизмом, то лучше чистый автоматный метод применять, а не АКОП.

Чистый автомат во первых очень сложный получится и его развивать будет невозможно. Да еще и его скорость будет зависить от компилятора, на сколько грамотно он все это разместит в jump-tables. А на SST можно запросто делать масштабируемые приложения, как это делается на Javascripte

Цитата
Что там есть прерывания ядра, которые работают быстрее прерываний АКОП-а

Kak это они могут быть быстрее, если в SST прерывание - это голый обработчик железного прерывания?
Мало того, он ведет себя точно так же, как и обычный пользовательский код. В нем можно выполнять все те же функции, что и в юзере, а не иметь 2 версии функций под прерывания и под треды.
AlexandrY
Цитата(brag @ Sep 15 2016, 10:17) *
не знаю что там у них за супер-цикл, лично у меня этот суперцикл выглядит следующим образом:
Код
while(true){
       sleep();
}

И он единственный во всем проекте. sleep - это обертка ассемблерной инструкции проца, если что, больше она ничего не делает.


А может sleep это волшебный супервизор?
или sleep() на самом деле переопределенный вызов суперцикла.
А у меня к сведению тогда главная задача в RTOS выгдядит вот так -

Код
IDLE;
biggrin.gif


Java и Javascript.., хм. Я бы тогда сказал Java Me Embedded и Jave SE это точнее отразит разницу в масштабе.

Кстати для любителей экзотики уже вышел Java ME Embedded 8.3 . Вот где масштабируемость в истинном смысле слова по полной программе!

brag
Может и супервизор, меня это не волнует, для меня это просто инструкция процессора.
ME/SE стиль похож, а в JS он другой, не похожий на типичную Java.
AlexandrY
Цитата(brag @ Sep 15 2016, 11:10) *
Может и супервизор, меня это не волнует, для меня это просто инструкция процессора.
ME/SE стиль похож, а в JS он другой, не похожий на типичную Java.


А вот это выдает подход к программированию, на мой взгляд.

Люди мало программирующие обращают внимание на стиль и синтаксис.
А люди много программирующие обращают внимание только на API и либы.

ME и SE - земля и небо c точки зрения API.

Кстати ваш пустой цикл со sleep-ом может говорить о том, что вы не знаете и не следите за реальной нагрузки своего процессора, т.е. на самом деле не занимаетесь риалтаймом.
Какой-то любительский гаджет со звуком?
А ? Угадал?
brag
За загрузкой проца я слежу отдельным событием по таймеру, использую встроенные средства в проц для подсчета тактов.
Проектов много разных, в том числе и на DSP, где код должен отрабатывать быстро.

А еще SST - это путь к функциональному программированию - где все переменные const(а значит нет side-эффектов), где нет циклов, но есть сборка мусора и рекурсия, где компилятор сам может менять порядок вызова функций местами(мало того - может выполнять разные функции параллельно на разных процессорах/ядрах), когда это не влияет на результат, например:
Код
void f(x){
   printf(x); // 1
   const y = sin(x); //2
   const z = cos(x); //3
   printf(y,z); //4
}

Так вот компилятор может запросто поменять местами шаги 2 и 3, или группу {2,3,4} поменять с 1 - то есть на экран сначала напечатается y,z, а потом уже x. Но не может поменять, скажем 3,4 тк 4 требует окончания выполнения 3.
Это очень продвинутое программирование, и для императивщика, а тем более блокинг-императивщика оно непонятное и непосильное sm.gif
Но программы в функциональном стиле короче, чем в императиве и имеют кучу преимуществ. Каких - читайте в гугле.

В SST тоже вещи могут происходить не в том порядке, в котором написано, тоже очень много констант и мало циклов - это уже шаги к функциональному стилю.
Например в SST вот этот код скорее всего выполнится наоборот(а точнее практически одновременно):
printf("Sending 'hello' over SPI\n");
spi->write("hello");
Сначала начнется отправка по spi, а потом уже printf.

Также SST - путь к декларативному программированию. У меня много задач оформлены в декларативном виде.
Рано или поздно императив уйдет, как устаревшая техника, но legacy код ессно будут поддерживать еще очень долго.
AlexandrY
Цитата(brag @ Sep 15 2016, 11:35) *
За загрузкой проца я слежу отдельным событием по таймеру, использую встроенные средства в проц для подсчета тактов.
Проектов много разных, в том числе и на DSP, где код должен отрабатывать быстро.


По таймеру?
Очень интересно. Прямо сейчас придумали?

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

На демке не получится, она под PC.
zltigo
QUOTE (AHTOXA @ Sep 14 2016, 22:00) *
Зря вы так ополчились на brag-а. Он говорит весьма интересные вещи.

Вещи на самом деле совершенно обычные, если действительно отбросить всю мишуру. Я к такому пришел первый раз несколько десятков лет назад, когда плотно занимался телефонией и городить многие тысячи процессов со своими стеками да и еще блокировками было просто нереально, посему и естественно получались и очереди и отсутствие (в пределах оговоренных пиковых нагрузок) блокировок. Второй раз лет 15 назад, пришел к этому, когда занялся FreeRTOS. В ней всякие семафоры и иже с ними, были реализованы, КАК МАКРОСЫ НА ОЧЕРЕДЯХ. Подумал я подумал и решил, что лишние сущности плодить незачем и попробовать на нативных очередях все и делать, но, естественно, в "классической" операционной системе.


QUOTE (AlexandrY @ Sep 15 2016, 11:27) *
Люди мало программирующие обращают внимание на стиль и синтаксис.
А люди много программирующие обращают внимание только на API и либы.

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


QUOTE (AlexandrY @ Sep 15 2016, 09:48) *
И если уж делать гибрид RTOS с другим механизмом, то лучше чистый автоматный метод применять, а не АКОП.

Да, типа автоматов.
sigmaN
Цитата
Подумал я подумал и решил, что лишние сущности плодить незачем и попробовать на нативных очередях все и делать, но, естественно, в "классической" операционной системе.
Вот и опять подтверждается то, что я всегда и говорил: оба подхода существуют не просто так. Там где надо - значит надо. А наш проповедник всех тотально на один стек пересадить хочет )) Не зависимо от задачи и процессора.
AlexandrY
Цитата(brag @ Sep 15 2016, 11:56) *
Да, по высоко-приоритетному таймеру беру за определенный промежуток времени количество тактов из регистра(для кортекс это DWT_xx) и прогоняю через фильтр.


Используете DWT_SLEEPCNT ?
Отсюда ссылку взяли http://stm32f4-discovery.net/2015/05/cpu-l...-for-stm32f4xx/?

Но в высоконагруженных embedded системах нету slеep-а.


Цитата(sigmaN @ Sep 15 2016, 13:06) *
Вот и опять подтверждается то, что я всегда и говорил: оба подхода существуют не просто так. Там где надо - значит надо.


Так потому и обсуждаем чтобы узнать где надо.

Пока все идет к тому, что метод TC годен для очень мелких микроконтроллеров в парой тройкой задач, которые автор в состоянии полностью написать сам не используя сторонний софт.
Ниша очень узкая.
Удобнее полностью перестроится под RTOS, и портировать ее даже на самые мелкие микроконтроллеры.
Сергей Борщ
QUOTE (sigmaN @ Sep 15 2016, 13:06) *
А наш проповедник всех тотально на один стек пересадить хочет ))
Странно. Я вижу человека, который рассказывает, как он делает и объясняет, почему он так делает и зачем. И еще толпу, которая пытается ему доказать, что он все делает неправильно. Причем некоторые из этой толпы вообще не понимают, о чем идет речь.
AlexandrY
Цитата(Сергей Борщ @ Sep 15 2016, 13:30) *
Странно. Я вижу человека, который рассказывает, как он делает и объясняет, почему он так делает и зачем. И еще толпу, которая пытается ему доказать, что он все делает неправильно. Причем некоторые из этой толпы вообще не понимают, о чем идет речь.


Эт Сергей такой добрый пока brag на ScmRTOS не покатил бочку. biggrin.gif
brag
Не оттуда, взял из документации на ARM. Но смысл тот же.
sleeр у меня есть всегда и мониторит загрузку проца отлично. Там, где подобного механизма нет - считаю загрузку вручную через хуки.
Я не любитель загружать проц по максимуму, всегда оставляю резерв на будущее.

Цитата
А наш проповедник всех тотально на один стек пересадить хочет )) Не зависимо от задачи и процессора.

Я никого не пересаживаю. Если нужны legacy блокинг-либы, либо коллектив/заказчик хочет блокинг, либо просто работаете над проектом, где уже все написано в блокинг и переход на нонблокинг будет очень сложным и бессмысленным - тогда ССТ Вам не подходит. Вы жестко привязаны к платформе, либам, языку, стилю и технике.
ССТ это для тех, кто хочет развиваться, осваивать и делать что-то новое.
Подход да, очень стар, даже РТОС тогда наверное не было. Но он имеет кучу своих преимуществ и удобств, особенно, если есть хороший мощный высокоуровневый язык.

Цитата
Эт Сергей такой добрый пока brag на ScmRTOS не покатил бочку. biggrin.gif

А че там катить, нормальная ОС, да еще и на плюсах. Хотя есть к чему придраться. Но и к любому коду можно придряться(моему в том числе). Но этот топик про SST, а не про ScmRTOS

Мы рассматриваем ССТ в техническом аспекте, его возможности и недостатки. Вопросов совместимости его из блокинг-либами касаться смысла нет - он с ними не совместим. Можете считать это недостатком. Для кого-то он решающий, а для кого-то - несущественный.
dxp
brag, с такими устремлениями вам надо копать в сторону ERLANG - там есть всё, что вы хотите: [настоящий, честный, а не приближение] функциональный язык, легковесные процессы и прочее. sm.gif Очень эффективен для решения ряда задач. Притом сугубо прикладной, не академический.
brag
Да копаю я Erlang, но пока много кода на плюсах сделано, так что пока идет переходной процесс.
XVR
bb-offtopic.gif
'Энтузизизм' это конечно хорошо, но надо знать меру sm.gif
Был у меня в студенческую пору (где то конец 80х) научный руководитель (который сам года 2 назад еще был студентом), и написал я (от нечего делать) простой оконный менеджер (a-la Windows, но в текстовом режиме), и даже добавил туда thread'ы (все это собиралось на Borland C++ 3.1). В принципе было похоже на Turbo Vision (от того же Borland), но на тот момент он еще не вышел в массы sm.gif

Принцип построения этой системы так вдохновил моего 'научного руководителя', что он бегал по всему НИИ и с диким воссторгом и пеной у рта рассказывал всем, какая это крутая штука. Кроме того, он начал тщательно исследовать ее внутренности и писать маленькие програмки на ней.
При этом он совершенно забил на свои прямые обязанности (времени не осталось) и чуть не вылетел с работы sad.gif

Что касается SST, то в общем и целом писать на ней сложнее, чем на традиционной RTOS (что бы brag не говорил), т.к. простую монолитную задачу придется разрезать на части, и обеспечить их вызов в правильной последовательности. Это крайне легко сделать для чисто последовательных задач (именно такие мы в основном тут и видели), и сложнее, если flow исходной задачи более сложное.

Чисто формально для преобразования монолитной задачи в набор задач для SST необходимо проделать такие шаги:
  1. Найти в задаче все точки, в которых производится ожидание внешних событий
  2. Сделать граф, в котором точки из п1 являются вершинами, а control flow пути между ними - дугами
  3. Весь код по дугам оформить в виде отдельных Task'ов
  4. Полученный набор Task'ов может быть запущен под SST

Это все отлично работает для линейного графа (что мы уже видели), хуже работает для произвольного графа в пределах одной функции, и совершенно не работает если есть вызовы функций с ожиданиями внутри и тем паче если такие вызовы динамические. Что бы заставить этот случай заработать, надо преобразовать вызываемые функции так, что бы точки ожидания оказались за их пределами (что мы и видели на примере printf). Это означает координальную переделку всего и вся rolleyes.gif

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

Посмотрите на вышеизложенный алгоритм - количество путей между узлами графа зависит от связанности графа (т.е. от того, из каких точек ожидание в какие мы можем попасть), и в предельном случае (если можно попасть из любой точки в любую) количество дуг будет экспотенциально зависить от количества узлов (связь все ко всем). А само количество узлов может линейно зависить от размера исходной программы. Таким образов при переводе в SST этот кодовый монстр вырастет в экспотенциальной прогрессии crying.gif

Что касается блокировок, синхронизации и пр, то все не так плохо, как тут было обрисованно. В нормально сделанной системе они должны быть локализованны и быть их должно немного. Если у вас это не так, и синхронизация размазана равномерным слоем по всей программе, то это неправильное проектирование архитектуры системы, а не родовой недостаток RTOS biggrin.gif

AlexandrY
Цитата(brag @ Sep 15 2016, 13:48) *
Там, где подобного механизма нет - считаю загрузку вручную через хуки.
Я не любитель загружать проц по максимуму, всегда оставляю резерв на будущее.


А теперь посмотрите сколько отладочной информации и инструментов есть у MQX.
И чего вы лишаетесь переходя на доморощенные решения.
Перегрузку процессора я вижу даже когда она намного превышает 100%

Нажмите для просмотра прикрепленного файла
brag
Цитата
И чего вы лишаетесь переходя на доморощенные решения.

Это я в курсе. Совместимости с блокинг-кодом тут нет. А инструментов своих хватает. Да и не работаю я под виндой, так что...

Цитата
Перегрузку процессора я вижу даже когда она намного превышает 100%

А это как, был проц на 72мгц а вдруг стал работать на 300? sm.gif

Цитата
Что бы заставить этот случай заработать, надо преобразовать вызываемые функции так, что бы точки ожидания оказались за их пределами (что мы и видели на примере printf).

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

Цитата
Это крайне легко сделать для чисто последовательных задач (именно такие мы в основном тут и видели), и сложнее, если flow исходной задачи более сложное.
...
Таким образов при переводе в SST этот кодовый монстр вырастет в экспотенциальной прогрессии crying.gif

Можно пример кода, а то я не понял в чем собственно проблема?
AHTOXA
Цитата(XVR @ Sep 15 2016, 18:29) *
bb-offtopic.gif
Был у меня в студенческую пору (где то конец 80х)
...
(все это собиралось на Borland C++ 3.1).
...
В принципе было похоже на Turbo Vision (от того же Borland), но на тот момент он еще не вышел в массы sm.gif


Ох, что-то с памятью у вас не то... sm.gif Borland C++ 3.1 вышел в 1992 году. И в нём уже был Turbo Vision.
zltigo
QUOTE (AHTOXA @ Sep 15 2016, 19:44) *
Ох, что-то с памятью у вас не то... sm.gif Borland C++ 3.1 вышел в 1992 году. И в нём уже был Turbo Vision.

Да, а то и позже. Вообще первый реально рабочий Борлондячий 1.01 С++ в 91 году появился. С него начинал сишный путь. Первым опытом была оконая библиотека sm.gif. В 80х плюсов у борланда не было вообще.
XVR
Цитата(zltigo @ Sep 15 2016, 22:56) *
Да, а то и позже. Вообще первый реально рабочий Борлондячий 1.01 С++ в 91 году появился. С него начинал сишный путь. Первым опытом была оконая библиотека sm.gif. В 80х плюсов у борланда не было вообще.
Возможно это был не BC 3.1, а нечто более раннее. Было это в 89-90 годах, и TV в нем точно не было (а С++ был)



Цитата(brag @ Sep 15 2016, 18:12) *
Можно пример кода, а то я не понял в чем собственно проблема?
Проблема в том, что это вылезет только на очень болльшом коде, такой пример тут не привести

brag
Цитата(XVR @ Sep 16 2016, 12:06) *
Проблема в том, что это вылезет только на очень болльшом коде, такой пример тут не привести

Так большой и не надо, приведите простой, а мы попытаемся прикинуть на сколько оно страшно будет при большом коде. На NodeJS пишут очень большие и сложные приложения, и ничего вроде как
Ну и еще не стоит забывать, что размер кода для мк довольно ограничен wink.gif
XVR
Ну хорошо. Что нгибудь вроде такого (code? - куски кода без ожиданий чего либо, wait? - точки ожидания, cond? - какие нибудь условия)
Код
code1();
while(cond1())
{
  code2();
  if (cond2()) {code3(); wait1(); code4();}
  while(cond3()) {code5(); wait2(); code6();}
  if (cond3()) {code7(); wait3(); code8();}
}
code9();

Еще можно такой паттерн:
Код
interface Worker {
virtual void work() =0;
};

void func(Worker* worker)
{
while(cond1())
{
   code1();
   worker->work();
   code2();
}
}

Реализация интерфейса Worker может заблокировать нить исполнения в любом месте внутри метода work()
TSerg
Цитата(XVR @ Sep 16 2016, 12:06) *
а нечто более раннее. Было это в 89-90 годах, и TV в нем точно не было (а С++ был)

Turbo C 87 г.
Turbo C++ 90 г.
Borland C++ 2.0 90 г.
Borland C++ 3.1 (OWL + TV) 92 г.
XVR
Цитата(TSerg @ Sep 25 2016, 21:57) *
Turbo C++ 90 г.
Вот он и был

brag
XVR, Большое спасибо за примеры.

Цитата(XVR @ Sep 25 2016, 21:13) *
Ну хорошо. Что нгибудь вроде такого (code? - куски кода без ожиданий чего либо, wait? - точки ожидания, cond? - какие нибудь условия)
Код
code1();
while(cond1())
{
  code2();
  if (cond2()) {code3(); wait1(); code4();}
  while(cond3()) {code5(); wait2(); code6();}
  if (cond3()) {code7(); wait3(); code8();}
}
code9();

Ну этот пример очень абстрактный(теоретический) да и стиль тут чисто(слишком) синхронный, мало того, слишком много неявных зависимостей - побочных эффектов. Например, по коду не видно как зависит cond3 от code5 и code6 и зависит ли вообще.
Современный стиль программирования предполагает избавление от подобных спагетти. Зависимости должны быть явные, побочные эффекты либо вовсе отсутствовать(функциональный стиль), либо их минимизация и приведение в понятный явный вид. Разбиение сложной задачи на более простые итд.
Я понятия не имею, какую конкретную задачу должен решать этот код. Скорее всего, имея конкретную задачу в асинхронном стиле код будет совсем другой, проще и понятнее.

Цитата(XVR @ Sep 25 2016, 21:13) *
Еще можно такой паттерн:
Код
interface Worker {
virtual void work() =0;
};

void func(Worker* worker)
{
while(cond1())
{
   code1();
   worker->work();
   code2();
}
}

Реализация интерфейса Worker может заблокировать нить исполнения в любом месте внутри метода work()

Ну тут аналогично. work должен быть неблокирующий, а зависимость cond1 должна быть определена, а то по коду не понятно, кто влияет на cond1 - code1, code2, worker или вообще кто-то другой, которого мы здесь не видим (какое-нибудь прерывание, например - привет гонки).
Пoдобного рода код очень сложно поддерживать и масштабировать. Он требует глубокого рефакторинга, возможно переделки всей программы с нуля biggrin.gif

Асинхронный стиль чем и прикольный, что написание подобного рода кода, даже такого простого, очень геморройно и практически невозможно. Он заставляет сразу писать понятный масштабируемый код.
Конкретная задача в нон-блокинге решается совсем другим способом, кардинально отличающимся от блокинга.
XVR
Правильно, о чем собственно и говорилось. Такой код практически невозможно выразить в SST парадигме (ну или очень сложно). Т.е. объявляем такой код 'неправильным', и 'требующим переделки всей программы с нуля'.
При таком подходе что угодно можно положить на SST, а если оно не ложится - то оно неправильное sm.gif

Кстати, то, что SST не требует никаких синхронихаций и блокировок, а так же то, что deadlock'и в нем невозможны, не соотвествует действительности.

Синхронизация - это не атрибут реализации потока управления (классические thread'ы или SST), а атрибут разделяемых данных.

Если в вашей программе (в SST) нет разделяемых данных, то никакая синхронизация конечно не нужна, а если они есть (например модификация переменной из 2х задач), то она появляется.
И если в thread'ах это решается mutex'ами на обращение к переменной из разных threado'ов, то в SST это решается глобальной блокировкой прерываний в низкоприоритетной задаче на время работы с переменной. Это можно сделать и в thread'ах - завести один mutex на все переменные и захватывать его (полная аналогия функциональности блокировки прерывания ы SST). При таком методе никаких гонок и deadlock'ов в thread'ах не будет, но это очень грубый (я бы даже сказал топорный) метод. Он способен полностью убить производительность любой системы.

И deadlock'и и SST возможны. Рассмотрим 2 thread'а (в SST их будет больше), каждая из которых читает по 1 байту из очереди, обрабатывает, и записывает от 0 до 3х байт в другую очередь. Среднее количество записываемых байтов где то 0.5. Задача 1 читает из очереди A и пишет в очередь B. А задача 2 читает из B и пишет в A. (Допустим, что есть еще задача 3 которая иногда пишет что то в обе очереди) Обе очереди имеют ограниченный размер, и если в них нет места для новых данных, то задача поставщик блокируется.
Рассморим ситуацию: в обоих очередях содержится максимально возможное количество данных, и обе задачи хотят прочесть по 1 байту (каждая из своей очереди) и записать по 3. Прлучим deadlock (как в классической thread модели, так и в SST)

А то, что в SST нет классических объектов синхронизации, таких как mutex, semaphore, event объясняется не тем, что они не нужны, а тем, что их невозможно представить в классическом виде в модели SST, т.е. програмисту придется их описывать руками (в виде набора задач).
(Хотя event там есть - по сути это постановка задачи в очередь)



Кстати, код в примере совсем не абстрактный/теоритический. Это вполне жизнеспособный код. Он например соответствует обработке какого нибудь пакетного действия с аппаратурой. Например, нам надо передать блок данных в прибор. При этом блок очень большой, и за один раз не умещается. Т.е. его надо нарезать на пакеты, и перед началом (и после окончания) каждого пакета необходимо дождаться готовности от аппаратуры.

Первый if - это ожидание готовности, внутренний while - передача блока, 2й if - ожидание готовности после передачи пакета, и внешний while - нарезка всего блока данных на пакеты.

brag
Цитата
При таком подходе что угодно можно положить на SST, а если оно не ложится - то оно неправильное sm.gif

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

Точно так же, как, например в функциональное программирование - оно не совместимо с императивным кодом (там нельзя написать i=i+1), но зато на нем можно решать любые задачи, правда код выглядит совсем иначе и императивщику его понять очень трудно - нужно кардинально перестраивать свой мозг. Зато этот код короче, очень легко масштабируется, легко дебажится и имеет кучу других преимуществ.

Цитата
Если в вашей программе (в SST) нет разделяемых данных, то никакая синхронизация конечно не нужна, а если они есть (например модификация переменной из 2х задач), то она появляется.

В современном софте разделяемой памяти быть не должно. Разделяемые должны быть высокоуровневые сущности, и то на худой конец.
Лучше вообще ничего не разделять. Указатель(ссылка) на обьект должен быть один единственный(в один момент времени), а все переменные должны быть const. К этому и стремимся.

Цитата
И если в thread'ах это решается mutex'ами на обращение к переменной из разных threado'ов, то в SST это решается глобальной блокировкой прерываний в низкоприоритетной задаче на время работы с переменной.
....
Он способен полностью убить производительность любой системы.

Это далеко не так. В SST рулят очереди сообщений, а они могут работать без блокировок вовсе(lock-free алгоритмы). Да и если даже и с блокировками(например, когда аппаратная поддержка lock-free слабая или ее нет вовсе), то они очень короткие(на несколько инструкций, обычно до десяти, сама реализация традиционных мютексов и переключение контекста требует гораздо более длинных блокировок) и выполняются строго в недрах движка самой очереди. За пределами очередей блокировок нет. Это и есть то кардинальное отличие блокинга от нон-блокинга,
Для пользователя, тобышь программиста, блокировок нет и быть не может.

Цитата
Обе очереди имеют ограниченный размер, и если в них нет места для новых данных, то задача поставщик блокируется. Рассморим ситуацию: в обоих очередях содержится максимально возможное количество данных, и обе задачи хотят прочесть по 1 байту (каждая из своей очереди) и записать по 3. Прлучим deadlock (как в классической thread модели, так и в SST)

Нет, если нет места, то строчка записи в очередь выкинет исключение или вернет ошибку. Такие ошибки надо обязательно обрабатывать, нормальные высоко-уровневые языки программирования(типа Rust) сами заставляют программиста это делать. Блокировок здесь нет.

Да и такого понятия, как чтение из очереди тоже надо опасаться и обходить стороной.
Код должен быть не вида: while((size=read(data)))process(data,size);
а должен быть такого вида: void on_data_available(const Data* data, int size){ process(data, size); }

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

Цитата
Кстати, код в примере совсем не абстрактный/теоритический. Это вполне жизнеспособный код. Он например соответствует обработке какого нибудь пакетного действия с аппаратурой. Например, нам надо передать блок данных в прибор. При этом блок очень большой, и за один раз не умещается. Т.е. его надо нарезать на пакеты, и перед началом (и после окончания) каждого пакета необходимо дождаться готовности от аппаратуры.

А вот это гораздо ближе к делу. В принципе - типичная задача для embedded, и решение ее должно уже быть готовое в виде некого шаблонного класса, которое нужно просто взять и подключить парой строчек.
Но рассмотрим это решение по-ближе, в неблокирующем стиле ессно. Оно не идеальное, я сам только учусь wink.gif
CODE
Collector collector(&input_queue, Block_size, Max_packet_size); // обьект, который разбивает блоки данных на пакеты.
// Реализация может быть разная, зависит от рода задачи, но обычно в нем буфера нет,
// просто некая стейт-машина подвязанная под очередь.
// событыия прихода данных из очереди он обрабатывает сам, нам не нужно об этом заботится.

// установим наши обработчики событий
collector.on_packet_ready = process_packet; // событие - пакет собран
device.on_ready_to_receive_packet = process_packet; // событие - готовность аппаратуры принимать пакет

// оба события завернуты на 1 обработчик
void process_packet(){
// условие, по сути это проверка состояния очередей, выполняется очень быстро - с десяток инструкций
if(collector.is_packet_ready() && device.is_ready_to_receive_packet()){
// запускаем отправку пакета
device.sendPacket(collector.currentPacket(), [](){
// теперь пакет полностью принят аппаратурой
// сигнализируем коллектор, что текущий пакет обработан и он нам больше не нужен.
collector.signal_packet_processed();
});
}
}


Повторюсь, решение еще не совсем красивое. Обычно, работа с аппаратурой проектируется так, что вообще делать ничего не нужно, драйвер сам берет из очереди столько данных, сколько может взять, а сборка данных в пакеты производится внутри драйвера через подобные классы-коллекторы(заготовленные заранее в виде простейших шаблонных классов) (пользователь драйвера их не видит). И типичный код выглядит как-то так:
Код
Device1Queue dev1; // отсюда
Device2Queue dev2;  // пишем сюда

dev1.on_data_ready = [](Data* data, int len){
    dev2.send(data,len, [](){
        dev1.signal_data_processed();
    });
};


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

В теории можно конечно нафантазировать чего хочешь, реализовать которое без блокировок будет практически невозможно. Но необходимость этих блокировок будет вызвана этой самой теоретической задачей sm.gif
В реальном мире все иначе - любая практическая задача, а тем более работа с аппаратурой ложится на асинхронщину, как влитая.
дальше философия, можно не читать, но для программиста философия - это очень важно, нынче задачи очень сложные и обычным методом влоб они не решаются, нужно выдумывать всякие философские абстракции.
Физический мир сам по себе асинхронный.
Возьмем к примеру простейшую схему - батарейка лампочка и выключатель. Лампочка не ждет пока батарейка будет заряжена или пока нажмут выключатель, это смешно sm.gif
В реальности - лампочка засветится только тогда, когда батарейка заряжена и выключатель включен. Если произойдет событие (сядет батарейка или выключат выключатель) - лампочка погаснет.
Именно так и должна работать асинхронная программа - на событиях, имеющих свой физический аналог(смысл), а не на бессмысленных(а тем более вечных) циклах.
sigmaN
Цитата
В современном софте разделяемой памяти быть не должно. Разделяемые должны быть высокоуровневые сущности, и то на худой конец.
Лучше вообще ничего не разделять. Указатель(ссылка) на обьект должен быть один единственный(в один момент времени), а все переменные должны быть const. К этому и стремимся.
Можно отсылку на труды классиков? Ну т.е. где бы эта концепция более подробно раскрывалась.
Kabdim
Цитата(sigmaN @ Sep 27 2016, 13:00) *
Можно отсылку на труды классиков? Ну т.е. где бы эта концепция более подробно раскрывалась.

Это куцый огрызок от функционального программирования. Собственно ФП изучить стоит практически любому программисту для расширения кругозора, но не в виде таких постулатов. rolleyes.gif
sigmaN
Цитата
Собственно ФП изучить стоит практически любому программисту для расширения кругозора, но не в виде таких постулатов.
Вот именно поэтому и интересно было бы почитать классиков по данному вопросу
Kabdim
С книгами мне лично было сложно... начинал читать несколько книг, но каждый раз не хватало понимания "зачем это", что бы довести до какого-то результата. Зато вот по этим курсам, которые ведет Одерски (создатель Скалы) всё пошло как по маслу. Одерски - вполне классик. sm.gif
XVR
Цитата(brag @ Sep 27 2016, 00:41) *
В современном софте разделяемой памяти быть не должно. Разделяемые должны быть высокоуровневые сущности, и то на худой конец.
Лучше вообще ничего не разделять. Указатель(ссылка) на обьект должен быть один единственный(в один момент времени), а все переменные должны быть const. К этому и стремимся.
Ой! Напоминает планы построения коммунизма sm.gif

Цитата
Да и если даже и с блокировками(например, когда аппаратная поддержка lock-free слабая или ее нет вовсе), то они очень короткие(на несколько инструкций, обычно до десяти,
Этого достаточно.

Цитата
сама реализация традиционных мютексов и переключение контекста требует гораздо более длинных блокировок)
Вопрос не в длинне блокировки (она и в обычных мьютексах может быть кототкой), а в их принципиальном наличии

Цитата
За пределами очередей блокировок нет. Это и есть то кардинальное отличие блокинга от нон-блокинга,
Для пользователя, тобышь программиста, блокировок нет и быть не может.
Опять ой! Это опять не так. Например, переменная counter меняется в 2х задачах:
Код
// Low priority -
if (counter > 10)
{
...
  counter -= 10;
...
}

// High priority
counter=5;

Если высокоприоритетная задача вытеснит низкоприоритетную после if, то после ее окончания и возобновления низкоприоритетной задачи, в counter в конце концов окажется -5, чего никогда не может случиться, если бы if выполнялся атомарно (или под заблокированными прерываниями). Что и требовалось доказать sm.gif

Цитата
Нет, если нет места, то строчка записи в очередь выкинет исключение или вернет ошибку. Такие ошибки надо обязательно обрабатывать, нормальные высоко-уровневые языки программирования(типа Rust) сами заставляют программиста это делать. Блокировок здесь нет.
Вы опять подменяете условия задачи на другие. Сделать неблокирующее помещение в очередь (в данном случае) позволит избежать deadlock'а. Но это совершенно не значит, что в SST вообще не может быть deadlock'а. дной постановке задачи (с блокирующим помещением в очередь) он есть. И в SST он никуда не денется.

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


Цитата
Да и такого понятия, как чтение из очереди тоже надо опасаться и обходить стороной.
Код должен быть не вида: while((size=read(data)))process(data,size);
а должен быть такого вида: void on_data_available(const Data* data, int size){ process(data, size); }
Если ваш process захочет записать 3 байта в другую очередь (как в исходной задаче), то и ваш 'такой' код тоже повиснет

Цитата
Это совсем другой стиль и мыслить надо здесь иначе.
Но второй код проще - не нужно создавать бесполезный цикл, не нужно выделять место под буфер(data), не нужно проверять ошибки(чтения), не нужно ничего читать, не нужно ждать, не нужно блокироваться.
Он и без вашего участия сам заблокируется sm.gif

Цитата
А вот это гораздо ближе к делу. В принципе - типичная задача для embedded, и решение ее должно уже быть готовое в виде некого шаблонного класса, которое нужно просто взять и подключить парой строчек.
Теперь сравните размер - 5 строк в исходном коде и более 10 в SST. А если вспомнить, что is_ready_to_receive_packet тоже мог ждать, то код еще вырастет

Цитата
В реальном мире все иначе - любая практическая задача, а тем более работа с аппаратурой ложится на асинхронщину, как влитая.
Не любая, а с более менее простой структурой управления (в идеале вообще линейная) и с небольшим набором возможных состояний. Для МК это так. Для систем побольше, уже не так.

Цитата
Именно так и должна работать асинхронная программа - на событиях, имеющих свой физический аналог(смысл), а не на бессмысленных(а тем более вечных) циклах.

Дались вам эти циклы. Далеко не все из них состоит. И преимущество thread перед SST состоит не в том, что в thread программе можно написать бесконечный цикл, а в том, что thread сохраняет текущий контекст выполнения, включая стек вызовов со всеми переменными. В SST вам придется самому организовывать хранение контекста: переменные на внутренних переменных класса (или в захваченных лямбдой переменных), а положение в графе управления (call stack в thread) - явным дроблением и выделением всех возможных точек входа/выхода и переходов.

Наличие или отсуствие синхронизации, deadlock'ов и разделяемых переменных не играет никакой роли - и thread и SST модели делают это концептуально одинаково.
brag
Да, это все огрызки. С ними возимся не из за того, что не можем дорасти до нормального ФП, а из за того, что железо, с которым работаем -императивное и слишком слабое и нормальные ФП-языки на нем запустить пока не удается.

Цитата
Опять ой! Это опять не так. Например, переменная counter меняется в 2х задачах:

Нет, в sst так писать нелься. Не должен counter меняться в 2х задачах.

Цитата
Но это совершенно не значит, что в SST вообще не может быть deadlock'а. дной постановке задачи (с блокирующим помещением в очередь) он есть. И в SST он никуда не денется.

Пример ситуации можно, когда будет dead-lock?

Цитата
Если ваш process захочет записать 3 байта в другую очередь (как в исходной задаче), то и ваш 'такой' код тоже повиснет

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

Цитата
Теперь сравните размер - 5 строк в исходном коде и более 10 в SST. А если вспомнить, что is_ready_to_receive_packet тоже мог ждать, то код еще вырастет

Ну это особенность языка, да и то большинство строк - комментарии sm.gif

Цитата
Не любая, а с более менее простой структурой управления (в идеале вообще линейная) и с небольшим набором возможных состояний. Для МК это так. Для систем побольше, уже не так.

Хотелось бы реальный пример такой задачи, мозг потренировать wink.gif

Цитата
В SST вам придется самому организовывать хранение контекста: переменные на внутренних переменных класса (или в захваченных лямбдой переменных), а положение в графе управления (call stack в thread) - явным дроблением и выделением всех возможных точек входа/выхода и переходов.

Это да. но хотелось бы реальную задачу, где именно сохранение контекста необходимо или сильно облегчало бы жизнь.

Цитата
Наличие или отсуствие синхронизации, deadlock'ов и разделяемых переменных не играет никакой роли - и thread и SST модели делают это концептуально одинаково.

Да, одинаково, поэтому делают active-object pattern и тогда программа стает такая же, как бы она была написана на SST (если все вызовы асинхронные).
Для просмотра полной версии этой страницы, пожалуйста, пройдите по ссылке.
Invision Power Board © 2001-2025 Invision Power Services, Inc.