Глобальные и не очень переменные и многозадачность (ESP32!)

Бывает придет руководитель проекта и, такой: “ребята, делаем однопользовательскую версию %software%, сроки поджимают”.
Ну и делаете, просто, быстро, с кучей глобальных переменных.
И тут внезапно: “ребята, концепт поменялся, надо многопользовательскую”.

Ну и чо теперь делать?
Городить какие-то per-user структуры, в которые запихивать наши глобальные переменные, которые теперь и не очень-то и глобальные и т.п.?
Переписывать весь код, с учетом многопользовательности?

А вот и нет.

Если объявить переменную как

static __thread int variable = 13; // по-старинке

или, что то же самое

static _Thread_local int variable = 13; // как положено

(обязательно static или extern)

То каждый отдельный процесс получит свою собственную копию “глобальной” переменной.

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

Пример из реальной жизни №1: мониторинг мотора лодочного Yamaha.

Потом, когда все было готово, само-собой понадобилось добавлять второй мотор :))))

Пример из реальной жизни №2: в свое время мне это очень помогло спортировать pppd под RTEMS/powerpc и сделать его многопользовательским: просто добавлением __thread перед глобальными переменными

2 лайка

Выпустим версию 1.0 в соответствии с ТЗ.
Следующая версия 2.0 может быть в измененном концепте.

Но мне все равно интересно: откуда в проекте (особенно, если он делается коллективом, а не маргиналом-одиночкой) куча глобальных переменных? (разумеется, если этот проект не на FORTRAN’е)

Смысл слов “техдолг” и “рефакторинг” этому “руководителю” проекта не знакомы. Я бы делал ноги из под такого руководства.

К синтаксису и описанному инструменту претензий не имаю.

Очень просто.

Открываете исходники file-utils какие-нибудь, или, там, исходники того же PPPD. Да или практически любые linux-утилиты, стандартные.

Там сплошняком везде. Чо еще… ну, какой-нибудь readline() или editline(). До недавнего времени errno был глобальный. Тащем-та он и сейчас глобыльный, но по методике выше - per-thread variable.

Какие еще примеры сделанные маргиналами одиночками… мммм.. ах, GCC же. Да, да, посмотрите его код, прозреете.

Просто у них есть такая забава, как fork(), и так они и выживают. Например, pppd, чтобы реализовать multilink ppp просто стартует несколько экземпляров самого себя, и прекрасно себя чувствует.

Веб-сервер Apache, тысячи их.

А у нас - нет никакого fork()'a

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

Чо делать? Куда буфер девать?

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

static буфер на стеке? ну , тогда будет проблема, о которой я уже тут писал как-то, когда значение в буфере будет портится.Какие еще варианты, кроме переписывания этого printf’a?

То-то.

А выход простой - дописать __thread к вашему глобальному буферу, и все.

:trollface:

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

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

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

1 лайк

Не, я не говорю, что это прям всегда выход. Иногда бывает так понаписано, что ой.

Я уже упоминал FORTRAN, причем подразумевал под ним не конкретный язык, а собирательное понятие.
Так что давайте отделять мух от котлет. Если мы имеем в виду разработки 30+ летней давности, то возникают серьезные сомнения в том, что в результате указанного вмешательства все пройдет как надо. Если же, как в исходном сообщении темы, речь идет о современной разработке, все-таки остается непонятным, откуда в ней куча глобальных переменных.

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

Ну извините, не знал.

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

Сколько в среднем скетче ардуино глобальных переменных, как считаете?

Вот и мне не понятно почему в блинке нельзя глобальные переменные использовать? С ними что программа медленнее работает?

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

1 лайк

Локальные они ближе, они перед глазами, а глобальные поди их сыщи! Особенно в блинке. С goto также.)

1 лайк

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

У меня примерно равно количеству включаемых файлов.
Расчет прост: пара файлов h/cpp - это класс, статическая переменная - экземпляр класса. Впрочем, экземпляр класса не обязан быть статической переменной либо указателем на нее. С другой стороны, потоки ввода вывода (особенно протокол отладки) зачастую бывают статическими. Поэтому количество статических переменных не обязано точно совпадать с количеством классов.
Но количество в любом случае близкое.

В блинке?
Прямо таки куча глобальных переменных?
Да их по пальцам одной руки можно пересчитать.

Кстати - да. Стек он практически гарантировано лежит в кэше, а сегмент данных обычно нет.

Глобальных переменных следует избегать. Это азы структурного программирования еще задолго до ООП. Для каждого глобала должно быть внятное объяснение - для чего ему такая область видимости и такое выделение памяти? Это если писать как ты, на С. Про С++ не стану тут распинаться.
Хороший тон - собирать настроечные глобальные параметры хотя бы в структуру.
В MISRA много чуши, типа неприятия тернарного оператора, и наш коллега ЕП называет стронников MISRA - “мисрастами” ;). Но про глобалы там все очень разумно написано. Кроме того, что почти во всех официальных конторах тебе придется писать код в соответствии с рекомендациями МИСРА. Так что лучше им следовать… ну хотя бы части из них, наиболее важной части.

Ну это само-собой.

Шутки шутками, но , например, ESP32 может быть сконфигурирован так, что стек задачи лежит во внешней SPI памяти, а глобальные переменные - в SRAM. Обращение к локальным будет дольше :)))

Если хорошенько задуматься, можно выдумать еще ерундее.

2 лайка