Цитата
При таком подходе что угодно можно положить на 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, и решение ее должно уже быть готовое в виде некого шаблонного класса, которое нужно просто взять и подключить парой строчек.
Но рассмотрим это решение по-ближе, в неблокирующем стиле ессно. Оно не идеальное, я сам только учусь

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();
});
};
Побочные эффекты(состояния) хоть и есть, но они сидят глубоко в библиотечных классах. Для пользователя код выглядит хоть и не чисто функциональным, но довольно близким к нему.
В теории можно конечно нафантазировать чего хочешь, реализовать которое без блокировок будет практически невозможно. Но необходимость этих блокировок будет вызвана этой самой теоретической задачей

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

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