brag 0 15 сентября, 2016 Опубликовано 15 сентября, 2016 · Жалоба Да копаю я Erlang, но пока много кода на плюсах сделано, так что пока идет переходной процесс. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 15 сентября, 2016 Опубликовано 15 сентября, 2016 · Жалоба :bb-offtopic: 'Энтузизизм' это конечно хорошо, но надо знать меру :) Был у меня в студенческую пору (где то конец 80х) научный руководитель (который сам года 2 назад еще был студентом), и написал я (от нечего делать) простой оконный менеджер (a-la Windows, но в текстовом режиме), и даже добавил туда thread'ы (все это собиралось на Borland C++ 3.1). В принципе было похоже на Turbo Vision (от того же Borland), но на тот момент он еще не вышел в массы :) Принцип построения этой системы так вдохновил моего 'научного руководителя', что он бегал по всему НИИ и с диким воссторгом и пеной у рта рассказывал всем, какая это крутая штука. Кроме того, он начал тщательно исследовать ее внутренности и писать маленькие програмки на ней. При этом он совершенно забил на свои прямые обязанности (времени не осталось) и чуть не вылетел с работы :( Что касается SST, то в общем и целом писать на ней сложнее, чем на традиционной RTOS (что бы brag не говорил), т.к. простую монолитную задачу придется разрезать на части, и обеспечить их вызов в правильной последовательности. Это крайне легко сделать для чисто последовательных задач (именно такие мы в основном тут и видели), и сложнее, если flow исходной задачи более сложное. Чисто формально для преобразования монолитной задачи в набор задач для SST необходимо проделать такие шаги: Найти в задаче все точки, в которых производится ожидание внешних событий Сделать граф, в котором точки из п1 являются вершинами, а control flow пути между ними - дугами Весь код по дугам оформить в виде отдельных Task'ов Полученный набор Task'ов может быть запущен под SST Это все отлично работает для линейного графа (что мы уже видели), хуже работает для произвольного графа в пределах одной функции, и совершенно не работает если есть вызовы функций с ожиданиями внутри и тем паче если такие вызовы динамические. Что бы заставить этот случай заработать, надо преобразовать вызываемые функции так, что бы точки ожидания оказались за их пределами (что мы и видели на примере printf). Это означает координальную переделку всего и вся :rolleyes: Более того, если таких функций много, и граф их вызовов чисто динамический, и если в него еще входят вызовы чего то библиотечного, то такую систему будет проще запустить на класической RTOS, чем на SST. Посмотрите на вышеизложенный алгоритм - количество путей между узлами графа зависит от связанности графа (т.е. от того, из каких точек ожидание в какие мы можем попасть), и в предельном случае (если можно попасть из любой точки в любую) количество дуг будет экспотенциально зависить от количества узлов (связь все ко всем). А само количество узлов может линейно зависить от размера исходной программы. Таким образов при переводе в SST этот кодовый монстр вырастет в экспотенциальной прогрессии :crying: Что касается блокировок, синхронизации и пр, то все не так плохо, как тут было обрисованно. В нормально сделанной системе они должны быть локализованны и быть их должно немного. Если у вас это не так, и синхронизация размазана равномерным слоем по всей программе, то это неправильное проектирование архитектуры системы, а не родовой недостаток RTOS Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AlexandrY 3 15 сентября, 2016 Опубликовано 15 сентября, 2016 · Жалоба Там, где подобного механизма нет - считаю загрузку вручную через хуки. Я не любитель загружать проц по максимуму, всегда оставляю резерв на будущее. А теперь посмотрите сколько отладочной информации и инструментов есть у MQX. И чего вы лишаетесь переходя на доморощенные решения. Перегрузку процессора я вижу даже когда она намного превышает 100% Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 15 сентября, 2016 Опубликовано 15 сентября, 2016 · Жалоба И чего вы лишаетесь переходя на доморощенные решения. Это я в курсе. Совместимости с блокинг-кодом тут нет. А инструментов своих хватает. Да и не работаю я под виндой, так что... Перегрузку процессора я вижу даже когда она намного превышает 100% А это как, был проц на 72мгц а вдруг стал работать на 300? :) Что бы заставить этот случай заработать, надо преобразовать вызываемые функции так, что бы точки ожидания оказались за их пределами (что мы и видели на примере printf). Да,от блокировок(ожиданий) надо избавлятся полностью, иначе будут заблокированы все задачи равного или более низкого приоритета. Сложнее или нет - не знаю, мне когда-то было сложно, но после хорошей практики стало так же просто. Это крайне легко сделать для чисто последовательных задач (именно такие мы в основном тут и видели), и сложнее, если flow исходной задачи более сложное. ... Таким образов при переводе в SST этот кодовый монстр вырастет в экспотенциальной прогрессии crying.gif Можно пример кода, а то я не понял в чем собственно проблема? Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
AHTOXA 18 15 сентября, 2016 Опубликовано 15 сентября, 2016 · Жалоба :bb-offtopic: Был у меня в студенческую пору (где то конец 80х) ... (все это собиралось на Borland C++ 3.1). ... В принципе было похоже на Turbo Vision (от того же Borland), но на тот момент он еще не вышел в массы :) Ох, что-то с памятью у вас не то... :) Borland C++ 3.1 вышел в 1992 году. И в нём уже был Turbo Vision. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
zltigo 2 15 сентября, 2016 Опубликовано 15 сентября, 2016 · Жалоба Ох, что-то с памятью у вас не то... :) Borland C++ 3.1 вышел в 1992 году. И в нём уже был Turbo Vision. Да, а то и позже. Вообще первый реально рабочий Борлондячий 1.01 С++ в 91 году появился. С него начинал сишный путь. Первым опытом была оконая библиотека :). В 80х плюсов у борланда не было вообще. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 16 сентября, 2016 Опубликовано 16 сентября, 2016 · Жалоба Да, а то и позже. Вообще первый реально рабочий Борлондячий 1.01 С++ в 91 году появился. С него начинал сишный путь. Первым опытом была оконая библиотека :). В 80х плюсов у борланда не было вообще.Возможно это был не BC 3.1, а нечто более раннее. Было это в 89-90 годах, и TV в нем точно не было (а С++ был) Можно пример кода, а то я не понял в чем собственно проблема?Проблема в том, что это вылезет только на очень болльшом коде, такой пример тут не привести Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 23 сентября, 2016 Опубликовано 23 сентября, 2016 · Жалоба Проблема в том, что это вылезет только на очень болльшом коде, такой пример тут не привести Так большой и не надо, приведите простой, а мы попытаемся прикинуть на сколько оно страшно будет при большом коде. На NodeJS пишут очень большие и сложные приложения, и ничего вроде как Ну и еще не стоит забывать, что размер кода для мк довольно ограничен ;) Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 25 сентября, 2016 Опубликовано 25 сентября, 2016 · Жалоба Ну хорошо. Что нгибудь вроде такого (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 25 сентября, 2016 Опубликовано 25 сентября, 2016 · Жалоба а нечто более раннее. Было это в 89-90 годах, и TV в нем точно не было (а С++ был) Turbo C 87 г. Turbo C++ 90 г. Borland C++ 2.0 90 г. Borland C++ 3.1 (OWL + TV) 92 г. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 26 сентября, 2016 Опубликовано 26 сентября, 2016 · Жалоба Turbo C++ 90 г.Вот он и был Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
brag 0 26 сентября, 2016 Опубликовано 26 сентября, 2016 · Жалоба XVR, Большое спасибо за примеры. Ну хорошо. Что нгибудь вроде такого (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 и зависит ли вообще. Современный стиль программирования предполагает избавление от подобных спагетти. Зависимости должны быть явные, побочные эффекты либо вовсе отсутствовать(функциональный стиль), либо их минимизация и приведение в понятный явный вид. Разбиение сложной задачи на более простые итд. Я понятия не имею, какую конкретную задачу должен решать этот код. Скорее всего, имея конкретную задачу в асинхронном стиле код будет совсем другой, проще и понятнее. Еще можно такой паттерн: 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добного рода код очень сложно поддерживать и масштабировать. Он требует глубокого рефакторинга, возможно переделки всей программы с нуля Асинхронный стиль чем и прикольный, что написание подобного рода кода, даже такого простого, очень геморройно и практически невозможно. Он заставляет сразу писать понятный масштабируемый код. Конкретная задача в нон-блокинге решается совсем другим способом, кардинально отличающимся от блокинга. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
xvr 12 26 сентября, 2016 Опубликовано 26 сентября, 2016 · Жалоба Правильно, о чем собственно и говорилось. Такой код практически невозможно выразить в SST парадигме (ну или очень сложно). Т.е. объявляем такой код 'неправильным', и 'требующим переделки всей программы с нуля'. При таком подходе что угодно можно положить на SST, а если оно не ложится - то оно неправильное :) Кстати, то, что 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 0 26 сентября, 2016 Опубликовано 26 сентября, 2016 · Жалоба При таком подходе что угодно можно положить на 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, и решение ее должно уже быть готовое в виде некого шаблонного класса, которое нужно просто взять и подключить парой строчек. Но рассмотрим это решение по-ближе, в неблокирующем стиле ессно. Оно не идеальное, я сам только учусь ;) 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(); }); }; Побочные эффекты(состояния) хоть и есть, но они сидят глубоко в библиотечных классах. Для пользователя код выглядит хоть и не чисто функциональным, но довольно близким к нему. В теории можно конечно нафантазировать чего хочешь, реализовать которое без блокировок будет практически невозможно. Но необходимость этих блокировок будет вызвана этой самой теоретической задачей :) В реальном мире все иначе - любая практическая задача, а тем более работа с аппаратурой ложится на асинхронщину, как влитая. дальше философия, можно не читать, но для программиста философия - это очень важно, нынче задачи очень сложные и обычным методом влоб они не решаются, нужно выдумывать всякие философские абстракции. Физический мир сам по себе асинхронный. Возьмем к примеру простейшую схему - батарейка лампочка и выключатель. Лампочка не ждет пока батарейка будет заряжена или пока нажмут выключатель, это смешно :) В реальности - лампочка засветится только тогда, когда батарейка заряжена и выключатель включен. Если произойдет событие (сядет батарейка или выключат выключатель) - лампочка погаснет. Именно так и должна работать асинхронная программа - на событиях, имеющих свой физический аналог(смысл), а не на бессмысленных(а тем более вечных) циклах. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться
sigmaN 0 27 сентября, 2016 Опубликовано 27 сентября, 2016 · Жалоба В современном софте разделяемой памяти быть не должно. Разделяемые должны быть высокоуровневые сущности, и то на худой конец. Лучше вообще ничего не разделять. Указатель(ссылка) на обьект должен быть один единственный(в один момент времени), а все переменные должны быть const. К этому и стремимся. Можно отсылку на труды классиков? Ну т.е. где бы эта концепция более подробно раскрывалась. Цитата Поделиться сообщением Ссылка на сообщение Поделиться на другие сайты Поделиться