Перейти к содержанию
    

Система, управляемая событиями и SST(super-simple tasker)

При проектировании SST - заведомо достаточно места в очереди. То есть избегать их переполнения.

Если уж переполнение произошло - нужно сделать восстановление - обычно это потеря данных, запись этого события в лог, и повторная их передача либо просто skip.

А это уже означает перепроектирование программы. Оригинальная программа даст deadlock. Т.е. сам по себе SST не в состоянии его предотвратить.

Что и означает, что заявление 'в SST никаких dedlockов быть не может' несколько не соотвествует действительности, не так ли?

 

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

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Оригинальная программа даст deadlock. Т.е. сам по себе SST не в состоянии его предотвратить.

Оригинальная программа на SST даст исключение.

 

Что и означает, что заявление 'в SST никаких dedlockов быть не может' несколько не соотвествует действительности, не так ли?

Смотря что подразумевать под deadlock-ом.

 

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

Смотря что подразумевать под потоком :)

Если параллельное исполнение кода (тру поток) - тогда можно добиться и в одном потоке. Если поток классической ОС (часто его называют Thread-ом) - тогда тоже можно и в одном потоке получить такой трудновоспроизводимый lock.

 

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

 

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Оригинальная программа на SST даст исключение.
Значит это не оригинальная программа. Она (оригинальная, на thread'ах) будет ждать освобождения очереди, и никаких исключений. Более того, если SST программа будет давать исключение на любое переполнение очереди, то это еще хуже, чем deadlock - она вылетит по исключению тогда, когда оригинальная будет продолжать работать (т.к. для deadlock'а нужно переполнение обоих очередей, а в SST версии чтобы умереть хватит переполнения и одной очереди)

 

Смотря что подразумевать под deadlock-ом.
Я уже давал определение, давайте послушаем ваше.

 

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

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

Но такая система является бесполезной на практике, она кроме выполнения бесполезных вычислений больше ничего не может.
Это почему не может? А работа по поллингу вместо прерываний? Или если у процессора выключить прерывания, то он сразу превратится в кирпич?

Я думал любой deadlock есть результат бага в программе.

Да, но не любая бага приводит к deadlock'у

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Это почему не может? А работа по поллингу вместо прерываний? Или если у процессора выключить прерывания, то он сразу превратится в кирпич?

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

С мютексами тоже можно работать по поллингу и тоже можно получить deadlock :)

Ну или кривая FSM(тоже по сути polling) тоже может перестать обрабатывать сообщения - при стечении определенных обстоятельств.

 

Более того, если SST программа будет давать исключение на любое переполнение очереди, то это еще хуже, чем deadlock - она вылетит по исключению

Никуда она не вылетит, исключение можно подловить и восстановиться или обработать ошибку сразу без использования исключения. При deadlock-e на мютексах/блокировках такое сделать не получится, разве что ради этого есть поддержка в ОС.

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

С мютексами тоже можно работать по поллингу и тоже можно получить deadlock :)
Очень хочу посмотреть на 'мьютексы на поллинге' :rolleyes:

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

При deadlock-e на мютексах/блокировках такое сделать не получится, разве что ради этого есть поддержка в ОС.
В некоторых есть

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

Да и юнит-тестирование быстро такие места(переполнение очередей) выявляет,
В оригинальной задаче никакое юнит тестирование это не выявит. Более того, никакие размеры очередей (кроме бесконечных) не дадут гарантии отсуствия dedalock'ов. И это все остается в силе и при реализации на SST (но не вашей, а оригинальной. Вы реализовали другую задачу)

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

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

То же самое происходит и при работе с очередями :)

 

Очень хочу посмотреть на 'мьютексы на поллинге' rolleyes.gif

Та что там смотреть, классика по сути

struct Mutex{
  void lock(){
    while(true){ // poll variable 'm'
      disable_irq();
      if(!m){
        m = true;
        enable_irq();
        return;
      }
      enable_irq();
      // we can do something else here, run FSM for example
    }
  }

  void unlock(){ m = false; }
  
  Mutex(): m(false){}

private:
    bool m;
}

Такой мютекс можно применять и без RTOS(а можно и с), например в классической FSM, я когда-то так делал, пока не пришел к SST-модели.

 

Что такое потоки в однопроцессорной системе? Это те же самые FSM с асинхронными прерываниями. Даже без асинхронных прерываний на FSM можно получить эффект deadlock-a, лок будет зависеть не от кода, а от стечения обстоятельств, точно так же, как это происходит в OS-thread-ах. В тредах лок зависит от хода переключения контекста(тоже по сути от событий-прерываний), в примере от XVR - от состояния очередей, в обычной FSM - от ее состояния и внешних или внутренних событий(прерываний, таймеров, регистров итп).

 

Я привык оперировать не синтетическими задачами, а реальными. В моем понимании классический deadlock это что-то типа этого:

class Account {
  double balance;

  void withdraw(double amount){
     balance -= amount;
  } 

  void deposit(double amount){
     balance += amount;
  } 

   void transfer(Account from, Account to, double amount){
        sync(from);
        sync(to);
           from.withdraw(amount);
           to.deposit(amount);
        release(to);
        release(from);
    }
}

Задача решена как-бы правильно и бага или не/недо-проверки ошибок здесь как бы нет. Но..

Реальная ситуация: клиент А отправляет клиенту B 100 рублей, в этот же момент клиент В отправляет клиенту А 50 рублей - и все, это может привести к deadlock-у. Я такие ситуации не раз ловил, очень трудно найти и исправить, особенно, если код гораздо сложнее.

 

Хотелось бы увидеть реальный(а не синтетический) пример на неблокирующих очередях, который привел бы к подобной ситуации.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

То же самое происходит и при работе с очередями :)
Если у вас прерывание может запустить более приоритетную задачу из очереди немедленно (не дожидаясь какого либо вызова SST интерфейса), то это уже параллельность. И это отличается от рассматриваемого случая. А если не может, то зачем такой SST нужен?

 

Та что там смотреть, классика по сути
Это называется Spin Lock, и он не является 'Мьютексом на поллинге' :) (Точнее это не тот 'поллинг', который применялся в вышеописанном случае)

 

Что такое потоки в однопроцессорной системе? Это те же самые FSM с асинхронными прерываниями. Даже без асинхронных прерываний на FSM можно получить эффект deadlock-a, лок будет зависеть не от кода, а от стечения обстоятельств, точно так же, как это происходит в OS-thread-ах. В тредах лок зависит от хода переключения контекста(тоже по сути от событий-прерываний), в примере от XVR - от состояния очередей, в обычной FSM - от ее состояния и внешних или внутренних событий(прерываний, таймеров, регистров итп).
Ага. И в SST тоже самое. Что мой пример и показал.

 

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

 

Я привык оперировать не синтетическими задачами, а реальными. В моем понимании классический deadlock это что-то типа этого:
Это один из вариантов dedlock'ов. Он не единственный. Кстати, в данном примере dedlock'а не будет :)

 

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

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

 

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

 

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я понимаю, что эхо с изменением тембра можно сделать без такой изощренной схемы

Вот это главное - SST заставляет делать вещи просто :) там, где нужны были куча потоков, в SST это один поток и зачастую даже сам SST не нужен

SST нужен для приоритизации задач, например когда есть риал-тайм звук и что-то тяжелое, типа декодировки jpeg. Надо чтобы jpeg не приводил к замиранию звука, тут и помогают приоритеты SST. Для обычных прикладных задач обычно хватает 1-3 приоритета.

 

Кстати, в данном примере dedlock'а не будет sm.gif

Будет:

transfer A->B:
  sync(A);  // from
  // context switch (on time quantum for example)

transfer B->A:
  sync(B); // from
  // context switch

transfer A->B: // continue
  sync(B); // to.  DeadLock !
  // context switch

transfer B->A: // continue
  sync(A); // to. Strong DeadLock finally

Редко возникающий, тяжело находимый deadlock.

 

Ну и контр-пример на SST, вернее без SST, один поток, async IO:

   void transfer(Account from, Account to, double amount){
        from.withdraw(amount);
        to.deposit(amount);
    }

Поскольку все Account-ы равноприоритетные, никакой синхронизации не требуется вовсе.

 

Синхронизация на очередях избавляет от очень капризных и тяжело находимых багов - гонок при переключении контекста/прерываниях. Я делал свой планировщик для перебора всевозможных комбинаций переключения контекста, типа Relacy http://www.1024cores.net/home/relacy-race-detector . Последний не использую потому что он не смог найти даже такую простую гонку: https://github.com/dvyukov/relacy/issues/3

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

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

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

При синхронизации на мютексах не столько страшен deadlock, сколько гонки. Наглядный пример я уже приводил https://github.com/dvyukov/relacy/issues/3 - хоть и ff и nexec защищены (тк они atomic), да и по ходу исполнения на них по отдельности гонок нет, но есть так называемая гонка высшего порядка, то есть 2 ресурса защищены по отдельности, но не защищены в совокупности и результат работы программы будет зависеть не от программиста, а от варианта хода переключения контекста.

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

При общении обьектов через очереди сообщений таких проблем нет.

 

При синхронизации на мютексах, кроме гонок, есть еще одна важная проблема - resource starvation. Это когда много потоков интенсивно используют один и тот же ресурс. Но кому-то из потоков удается его получать, а кто-то так и не дожидается его освобождения.

По аналогии с лифтом - лифт ездит между первым и 4м этажом, тк там очень много народу, а один человек на 9м этаже так и не смог дождаться лифта.

Это вторая проблема после гонок.

С такой проблемой я тоже очень часто стыкался. Чаще всего это когда на шине SPI висит одно медленное устройство(типа термометра), а второе быстрое(скажем АЦП), занимающее 95% SPI. Потоки, работающие с этим быстрым устройством хаотично делятся мютексом шины, а потоку, работающему с термометром мютекс так и не достается. Хуже, если на шине не 2 устройства, а штук 5 разных.

При работе с SPI через очереди такой проблемы нет - все получают доступ к шине строго в порядке очереди.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Вот это главное - SST заставляет делать вещи просто :)

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

 

Будет:

transfer A->B:
  sync(A);  // from
  // context switch (on time quantum for example)

transfer B->A:
  sync(B); // from
  // context switch

В transfer B->A первым выполняется sync(A). Так что dedlock'а не будет :)

 

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

С этим никто не спорит. Синхронизация на очереди (она же сериализация, она же mailbox) является примитивом синхронизации более высокого уровня, чем голые мьютексы, семафоры и критические секции. Но за это преимущество приходится платить эффективностью. А в обычных (thread based) системах и способом использования - очередь требует event driven стиля (как в SST). Так что тут SST рулит однозначно.

 

К сожалению сам SST требует этот самый event driven способ построения программы, и принципиально не может использовать обычную flow модель. Для того, что бы безболезненно использовать очереди, это слишком высокая цена :rolleyes:

 

Я вижу одно неоспоримое преимущество SST - скромные требования к памяти (стеку).

 

Отсюда следует, что ниша чистого SST - это мелкие МК (но не слишком мелкие, т.к. на совсем маленьких МК не может быть очень сложных программ, и обычный Super Loop позволит сделать то же самое, что и SST, но без какого либо управляющего кода/tasker'а вообще)

 

Еще вижу, что можно использовать комбинированный подход - SST поверх обычных thread'ов. Эта комбинация мне кажется супер удобной. То, что просто ложится на SST будет запущено на нем, а все остальное - обычная программа. Например, если надо в процессе какой то сложной обработки (параллельной и запутанной) выдать результат в виде нескольких байтов в SPI железку, то запуск цепочки вывода в SST части будет очень простым и логичным действием. Сама цепочка вывода тоже будет достаточно простой и логичной.

Если же это делать порождением отдельного thread'а, то с одной стороны это будет явный overkill, а с другой это может оказаться менее прозрачным (в коде), чем запуск SST цепочки.

 

PS. Кстати, ваша реализация SST умеет запускать приоритетные задачи поверх менее приоритетных по аппаратному прерыванию? Если да, то как это реализованно?

 

 

 

При синхронизации на мютексах не столько страшен deadlock, сколько гонки. Наглядный пример я уже приводил - хоть и ff и nexec защищены (тк они atomic), да и по ходу исполнения на них по отдельности гонок нет, но есть так называемая гонка высшего порядка, то есть 2 ресурса защищены по отдельности, но не защищены в совокупности и результат работы программы будет зависеть не от программиста, а от варианта хода переключения контекста.

 

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

 

            if(ff($)==0){
                ff($)++;

 

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

 

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

 

Ни в коем случае. В данном случае нужно взять всю работу с ff и nexec в критическую секцию (это самое простое решение).

 

При общении обьектов через очереди сообщений таких проблем нет.

 

Разумеется, если програмист не умеет работать с разделяемыми ресурсами с помощью низкоуровневых примитивов синхронизации, ему лучше поручить это кому нибудь другому (очереди например). Или вообще завязать с програмированием и идти продавать пирожки :smile3046:

 

При синхронизации на мютексах, кроме гонок, есть еще одна важная проблема - resource starvation. Это когда много потоков интенсивно используют один и тот же ресурс.

 

Это проблема, но не проблема мьютексов, а проблема честного планирования доступа к ресурсу.

 

При работе с SPI через очереди такой проблемы нет - все получают доступ к шине строго в порядке очереди.

 

Да, очередь это один из возможных вариантов такого разделения. Но она тоже не покрывает всех возможных вариантов.

Например - у вас на SPI шине висит 1000 контролеров лампочек (сами лампочки например на пульте управления АЭС), и мотор, управляющий положением графитовых стержней в самом ядерном реакторе. Ваша программа управления определила, что с реактором все Ок, и выдала команду зажечь 500 зеленых лампочек, показывающих, что все Ок. В это время пролетающий мимо протон попал в урановый стержень в реакторе, и цепная реакция пошла в разнос. Программа это определила, и выдала экстренную команду мотору опустить стержни-поглотители. Но вот беда, в очереди SPI уже стоит 500 команд на зажигание лампочек, и пока дойдет очередь до мотора, опускать уже будет нечего - реактор расплавится :crying:

 

И в принципе такая задача (об разделении доступа к ресурсу, а не об печальной участи АЭС) очень похожа на задачу планирования task switch в современных ОС, и для ее решения применяют как чистую очередь (round robin алгоритм переключения), так и гораздо более изощренные алгоритмы (с приоритетми, классами приоритетов, priority boost'ами, наследованием приоритетов и пр)

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

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

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

 

transfer B->A первым выполняется sync(A). Так что dedlock'а не будет sm.gif

С чего бы это? Первым идет лок from, затем to.

transfer A->B : from=A, to=B

transfer B->A : from=B, to=A

И все, deadlock (раз в год).

 

К сожалению сам SST требует этот самый event driven способ построения программы, и принципиально не может использовать обычную flow модель. Для того, что бы безболезненно использовать очереди, это слишком высокая цена rolleyes.gif

Не спорю, цена высокая и платить нужно временем привыкания к event-driven-модели, ну и несовместимостью с flow-model кодом.

 

PS. Кстати, ваша реализация SST умеет запускать приоритетные задачи поверх менее приоритетных по аппаратному прерыванию? Если да, то как это реализованно?

Конечно. Без этого это уже не SST, а обычный одно-приоритетный код (типа NodeJS). Реализовано по разному на разных платформах. На Cortex через связку PendSV/SVC (где-то в этой ветке приводил ассемблерный код).

На других камнях через вложенные прерывания - при входе в обработчик разрешаются другие прерывания, а при выходе - запускается планировщик, который достает из очереди самую приоритетную задачу, приоритет которой выше текущей и выполняет ее (если такая имеется). В принципе так же, как в оригинальной статье по SST http://www.embedded.com/design/prototyping...r-Simple-Tasker

 

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

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

Я акцентировал внимание на двух переменных. Этот код(if в оригинале был защищен) из реальной практической задачи, где я спустя месяца 2 поймал гонку. А хуже того, если переменных/ресурсов не 2, а больше. Работать с таким вообще невозможно(годы уйдут на отладку) выход один - покрывать все одним локом, но это тормоз жуткий.

 

Ни в коем случае. В данном случае нужно взять всю работу с ff и nexec в критическую секцию (это самое простое решение).

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

 

Разумеется, если програмист не умеет работать с разделяемыми ресурсами с помощью низкоуровневых примитивов синхронизации, ему лучше поручить это кому нибудь другому (очереди например). Или вообще завязать с програмированием и идти продавать пирожки smile3046.gif

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

Почти любая сложная программа на мютексах имеет гонки, при чем такие запутанные, что ручным анализом кода их выявить невозможно. Да и инструментов не так много(можно сказать их нет), и сама симуляция сложной программы(перебор всевозможных вариантов переключения контекста) может занимать месяц времени на 4-ядерном 3ггц Core-i7 процессоре. Да и то это еще надо уметь описать assert-ы для симулятора, это тоже очень не просто.

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

 

Но вот беда, в очереди SPI уже стоит 500 команд на зажигание лампочек, и пока дойдет очередь до мотора, опускать уже будет нечего - реактор расплавится crying.gif

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

Именно SST решает эту проблему без изощренных алгоритмов планировки задач ОС.

В то время как сериализация на мютексах рано или поздно приведет к взрыву реактора - 500 лампочек начнут хватать мютекс хаотично, а времени для движка не останется, и наследование приоритетов не поможет. :)

 

Имхо, в нынешнее время единственное применение мютексам - защита данных внутри самой очереди, и то на системах, где нет другого механизма :)

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Отсюда следует, что ниша чистого SST - это мелкие МК (но не слишком мелкие, т.к. на совсем маленьких МК не может быть очень сложных программ, и обычный Super Loop позволит сделать то же самое, что и SST, но без какого либо управляющего кода/tasker'а вообще)

Как по мне, SST подходит под контроллеры от уровня Atmega8 до STM32F407, а так же практически на все DSP. На что-то более крупное нужно что-то стандартное(например linux)

Реализация SST настолько проста, что даже если у вас 1 приоритет(как в NodeJS/Javascript), то выкидывать его нет смысла - всегда работаешь с одним инструментом.

 

Еще вижу, что можно использовать комбинированный подход - SST поверх обычных thread'ов. Эта комбинация мне кажется супер удобной. То, что просто ложится на SST будет запущено на нем, а все остальное - обычная программа. Например, если надо в процессе какой то сложной обработки (параллельной и запутанной) выдать результат в виде нескольких байтов в SPI железку, то запуск цепочки вывода в SST части будет очень простым и логичным действием. Сама цепочка вывода тоже будет достаточно простой и логичной.

Если же это делать порождением отдельного thread'а, то с одной стороны это будет явный overkill, а с другой это может оказаться менее прозрачным (в коде), чем запуск SST цепочки.

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

 

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

 

Я думаю, если использовать смесь блокинг(тот же обычный printf) и нон-блокинг кода, то нужно это делать в классических тредах, возможно через active object. Но ни в коем случаи не использовать мютексы, а использовать высокоуровневые обьекты, а мютексы пусть используют разработчики этих обьектов внутри их.

 

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

void Client::do_socket_connect(){
    settings->readSetting(&settings_union.host, [this](){
        settings->readSetting(&settings_union.port, [this](){
            socket->connect(settings_union.host.data, settings_union.port.data);
        });
    });
}

Нужно заботится о том, где и как хранить обьекты host и port, немного некрасивый вид(callback hell назревает).

 

В блокинг модели это бы выглядело так:

void Client::do_socket_connect(){
    Settings::Item<Settings::ID_Host> host;        
    settings->readSetting(&settings_union.host);

    Settings::Item<Settings::ID_Port> port;
    settings->readSetting(&port);

    socket->connect(host.data, port.data);
}

 

Но если копнуть глубже... В первом варианте мы однозначно знаем сколько RAM потребуется выделить, а это большой + для embedded. В случаи с хранением этoго дела на стеке ситуация гораздо хуже (но удобнее).

Потом если это не embedded или есть памяти по-больше - можно выделять память в куче(или из пула фиксированных блоков - очень быстро и constant time), а удалится это все автоматически, через smart pointer - удобства не меньше, чем со стеком и затраты производительности копеечные.

Но это не так важно.

Важно другое - что, если нам на этот блок действий надо нацепить таймаут? Скажем, все эти 4 действия должны выполниться за 5 секунд(именно так и есть в оригинальной задаче, из которой я этот код выдрал). Если не успело - отменить и выдать ошибку.

На SST это сделать очень просто - в конце(или в начале, это вовсе не важно) просто запустить таймер

void Client::do_socket_connect(){
// ... 

   timer.start([this](){
       socket->abort();
       settings->abort();
       printf("do_socket_connect timeout\n");
   });
}

Реализовать такое на потоках - это довольно жутко, можете попробовать :)

Если abort в SST - это по сути просто замена callbacka(или нескольких) на пустышку, то как абортануть поток, если он завис в ожидании мютекса? Ведь settings->read - довольно тяжелая и сложная задача - поиск в флеш-памяти файла, поиск в файле строчки с данным settingom, куча проверок итп. Аналогично и с socket->connect.

Да и потоков ради этого всего понадобится как минимум 2, если не больше.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

С чего бы это? Первым идет лок from, затем to.

transfer A->B : from=A, to=B

transfer B->A : from=B, to=A

И все, deadlock (раз в год).

пардон, не обратил внимание, что from и to это формальные параметры. Посыпаю голову пеплом, вы правы - будет dedlock

 

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

Я акцентировал внимание на двух переменных. Этот код(if в оригинале был защищен) из реальной практической задачи, где я спустя месяца 2 поймал гонку.

Да, код жутковатый :cranky:

 

Ну так критическая секция(запрет прерываний) - это и есть глобальнейший мютекс.
Не обязательно запрет прерывания. Это может быть запрет входить в блок кода (как на Windows CRITICAL_SECION). Но она должна быть поставлена на все места, где работают с этими переменными. И да, может получиться тормоз :rolleyes:

 

А что, если эти ff и exec не просто переменные, а сложные обьекты, хуже - работа с аппаратурой? Сами они защищены по отдельности, но не защищены в совокупности.
Защищать надо именно совокупность. Какими методами это делается не важно, главное результат

 

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

 

Почти любая сложная программа на мютексах имеет гонки, при чем такие запутанные, что ручным анализом кода их выявить невозможно. Да и инструментов не так много(можно сказать их нет), и сама симуляция сложной программы(перебор всевозможных вариантов переключения контекста) может занимать месяц времени на 4-ядерном 3ггц Core-i7 процессоре. Да и то это еще надо уметь описать assert-ы для симулятора, это тоже очень не просто.
Если в программе много мьютексов и все они повязанны друг за друга, то в этом случае нужно переходить на более высокоуровневые примитивы (очереди например). Но только в этом случае. Совсем не обязательно всех и вся в принудительном порядке загонять под эти очереди

 

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

Именно SST решает эту проблему без изощренных алгоритмов планировки задач ОС.

Вы опять путаете причину со следствием. Динамические приоритеты появились не потому что планировщик в ОС такой сложный, а именно такой сложный планировщик появился потому, что возникла потребность в динамических приоритетах.

Кстати, очередь с приоритетами не аналогична приоритетам задач в планировщике ОС. Если в очередь поставить задачу с меньшим приоритетом, а потом постоянно добавлять задачи с большим, то задача с меньшим приоритетом никогда не исполнится. А ОС все же будет исполнять задачу с меньшим приоритетом.

 

В то время как сериализация на мютексах рано или поздно приведет к взрыву реактора - 500 лампочек начнут хватать мютекс хаотично, а времени для движка не останется, и наследование приоритетов не поможет. :)
Сериалзация на мьютексах не делается

 

Как по мне, SST подходит под контроллеры от уровня Atmega8 до STM32F407, а так же практически на все DSP.
Для STM32F407 уже можно и обычную ОС (типа scmRTOS или freertos), памяти у него хватит, а заниматься переучиванием себя под SST выйдет дороже. Использование SST в этом случае оправданно если вы эксперт в ней, и у вас уже есть большие наработки, и event drive стиль для вас привычен. Но на этом форуме похоже вы один такой :laughing:

 

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

 

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

 

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

 

Реализовать такое на потоках - это довольно жутко, можете попробовать
Даже пробовать не буду :)

 

то как абортануть поток, если он завис в ожидании мютекса?
Для этого обычно используют примитивы захвата с timeout'ом

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну как то так и делается. Лично я голые мьютексы не использую вообще, а как минимум делаю на них scoped критические секции. Захвата 2х и более мьютексов стараюсь избегать всеми силами и переходить на более высокоуровневы примитивы синхронизации. В 99% хватает scoped критической секции, так что к очереди мне приходилось использовать не более пары раз (а захват 2х мьютексов кажется вообще не приходилось)

 

Я где-то здесь недавно задавал вопрос. Нужно было сделать запрос и подождать готовности ответа, в частности функция i2c_read. Ничего лучше двух мьютексов не нашлось. На захвате первого ждем готовности интерфейса, на втором готовности ответа. Только второй примитив не мьютекс, а семафор. Можно как-то лучше? На очередях получается, еще сложнее.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

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

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...