Как стать автором
Обновить
4.25
Рейтинг

Параллельное программирование *

Распараллеливаем вычисления

Сначала показывать
Порог рейтинга

Технология автоматного программирования для ПЛК на языке LD

Параллельное программирование *Промышленное программирование *

В предыдущих статьях мы фрагментарно описали практику автоматного программирования для ПЛК. Здесь мы сведем все в одном месте и кое-что добавим. Ответы на вопросы, которые все же могут возникнуть после прочтения данного материала, можно найти в ранее написанных статьях автора. Перечень базовых статей следующий:

1. Автоматное программирование: определение, модель, реализация.

2. Вот, как просто! Автоматы в деле. Для ПЛК фирмы DELTA.

3. Автоматы в деле. Штабелер. Засады ПЛК.

Задание на проектирование программы

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

После нажатия кнопки «Сброс» (прокат останавливается, переходя в режим паузы) штабелер должен войти в режим паузы. Повторное нажатие кнопки выполняет реальный сброс системы управления. Продолжить прокат, находясь в ситуации паузы,  можно с помощью кнопки «Прокат». Штабелер, находясь в режиме «Автомат», может входить в тот же режим паузы, но после формирования текущего листа.   Работа штабелера в режиме системы «Полуавтомат» несколько отличается от работы в режиме «Автомат». В первом случае он останавливается после выполнения проката и ожидает срабатывания гильотины (в режиме «Полуавтомат» она запускается вручную). Дождавшись, он сбрасывает лист и перемещается в заключительную позицию. Из нее нажатием кнопки Штабелер «крыло» возвращается в исходное состояние. В режиме «Автомат» перемещение в заключительную позицию происходит только после выполнения задания.

Читать далее
Всего голосов 1: ↑1 и ↓0 +1
Просмотры 1.1K
Комментарии 41

Новости

Автоматы в деле. Штабелер. Засады ПЛК

Параллельное программирование *Промышленное программирование *

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

Запуск и режимы работы штабелера

Система управления линией профилирования металла поддерживает три базовых режима - ручной, полуавтоматический и автоматический. В программе им соответствуют реле - M9, M10, M11. Штабелер имеет всего два режима работы, названных  далее ручным и автоматическим. В ручном режиме работы системы он работает соответственно в ручном режиме, а в остальных - в автоматическом.

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

На рис.1 представлен код, который запускает в работу штабелер, устанавливая единичное значение флагу bПускШтабелера. При этом текущий режим ему задается флагом bРежимШтабелера, нулевое значение которого определяет ручной режим работы, единичное - автоматический.

Читать далее
Всего голосов 4: ↑4 и ↓0 +4
Просмотры 1.5K
Комментарии 31

Параллелизм в алгоритмах — выявле́ние и рациональное его использование. Возможности компьютерного моделирования

Open source *Алгоритмы *Lua *Параллельное программирование *

С тех пор как мир возник во мгле

Ещё никто на всей земле

Не предава́лся сожаленью

О том, что о́тдал жизнь ученью.

Абу Абдалла́х Рудаки́, Бухара, около 860÷941.

 

Нет ничего на свете более интересного лично для

Исследователя и одновременно полезного для

Человечества, чем позна́ние окружающего Мира.

Валерий Баканов. Крым, Щёлкино/Казантип, август 2022.

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

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

Ряд лет интересом пользовалось изучение параметров вычислительной трудоёмкости (фактически зависимости числа вычислительных операций от размерности обрабатываемых данных) для различных алгоритмов. Параметры параллелизма в алгоритмах – очередная сторона многогранной  сущности “алгоритм”.  В современной ситуации отечественным разработчикам придётся самостоятельно исследовать и решать все связанные с автоматизированной обработкой данных вопросы – время “неограниченной халявы” (когда можно было десятилетиями бездумно копировать западные разработки в области архитектуры и готовых решений аппаратной и программной частей) закончилось.

Читать далее...
Всего голосов 3: ↑3 и ↓0 +3
Просмотры 2K
Комментарии 10

«Верьте аль не верьте», но есть и такое… Шаговое программирование

Параллельное программирование *Промышленное программирование *

А что сказка дурна — то рассказчика вина.
Изловить бы дурака да отвесить тумака,
ан нельзя никак — ведь рассказчик-то дурак!
А у нас спокон веков нет суда на дураков!..
Леонид Филатов.

При обсуждении предыдущей статьи буквально, как "чертик из табакерки", выскочил вид программирования, названный шаговым. Автор был готов к подобному, поскольку был знаком ним, хотя, может быть, и "шапочно". В документации на ПЛК такое программирование носит, правда, название пошагового управления (видимо, это и сбивало меня на термин "пошаговое программирование"). Но это не столь принципиально, чтобы по этому поводу «ломать копья». Важна суть, а именно в силу ее далее эти термины будем считать равноправными.

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

Читать далее
Всего голосов 5: ↑4 и ↓1 +3
Просмотры 2.6K
Комментарии 13

Вот, как просто! Автоматы в деле. Для ПЛК фирмы DELTA

Параллельное программирование *Промышленное программирование *

Промышленный логический контроллер (ПЛК) - это тот же компьютер, но попроще. В нем есть все или почти все, что есть в любом ПК, но только, может,  в меньшем объеме или не такой производительности. Но зато он может работать там, где обычный компьютер неприменим. У ПЛК есть то, что делает работу с ним проще при управлении оборудованием.  Например,  наличие "на борту" каналов ввода/вывода дискретных логических сигналов. Его программирование специфично. Выбор языков программирования достаточно ограничен, по  большому счету их всего-то пять, и определяется стандартом МЭК 61131-3 [1]. И этого, как убеждает практика, по большому счету вполне достаточно.

Выбор ПЛК фирмы DELTA, кроме наличия собственной IDE, предоставляет доступ к широкому перечню периферийного оборудования. Фирменное ПО, как минимум, удобнее тем, что не требует особой настройки и «в один клик» работает на всей линейке технических средств фирмы. Минусы могут проявиться в отставании от передовых тенденций программирования. Но для ПЛК это не самая большая проблема, т.к. языки, определяемые стандартом, достаточно консервативны, а их настройка под разные типы ПЛК, как правило, не так уж сложна.

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

Читать далее
Всего голосов 7: ↑3 и ↓4 -1
Просмотры 2.6K
Комментарии 77

Простые highload паттерны на Go

Программирование *Go *Параллельное программирование *
Из песочницы

Привет, Хабр! Меня зовут Агаджанян Давид, хочу поделиться некоторыми инженерами рекомендациями, которые часто на моем опыте помогали держать highload нагрузку не прибегая к хардкору. Примеры будут на Go. Эти подходы довольно хорошо известны, но как мне кажется они недооценены и многие этими подходами пренебрегают. Если вы впервые видите их, то рекомендую хотя бы попробовать реализовать в своих проектах и провести бенчмарки, возможно вы будете приятно удивлены..

Читать далее
Всего голосов 32: ↑28 и ↓4 +24
Просмотры 10K
Комментарии 5

Автоматное программирование: определение, модель, реализация

Параллельное программирование *

Термин «автоматное программирование» (АП) был введен в широкую практику в 90-х годах прошлого века [1, 2], хотя о применении автоматов в программировании шла речь задолго до этого. R первым упоминаниям уже начала 70-х годов можно отнести метод введения переменной состояния или, по-другому, метод преобразования неструктурированных программ Ашкрофта и Манны [3]. За прошедшее время сформировалось достаточное число его поклонников и не меньшее число критиков. Если говорить об их разногласиях, то в их основе отсутствие формального определения АП и поверхностное восприятие его возможностей. Из-за этого автоматное программирование формируется интуитивно, что и приводит к противоречивым его формам, порой, мало похожим на первоисточник – модель конечного автомата.

Читать далее
Всего голосов 12: ↑7 и ↓5 +2
Просмотры 3.2K
Комментарии 39

Конкурентность в Go: пять примеров

Блог компании МТС Программирование *Go *Параллельное программирование *

Привет, Хабр! Я Артем Чаадаев, Golang-разработчик в МТС Digital. В этой статье я собрал примеры использования конкурентного кода в Go. Хотите узнать, как писать конкурентный код? Значит, вам сюда.

Добро пожаловать под кат!

Читать далее
Всего голосов 31: ↑27 и ↓4 +23
Просмотры 13K
Комментарии 6

Вот, как просто! Балакиревская (автоматная) архитектура процессоров

Высокая производительность *Параллельное программирование *Процессоры Будущее здесь

Рано или поздно и Вы зададитесь вопросом, каким будет будущее процессоров. Достижения современных фабрик типа TSMC говорят, что достигнут максимум наших технологических возможностей. В результате каждый последующий технологический этап дается все большим трудом и многократно возрастающими затратами. Для транзисторов счет пошел на единицы атомов и потому, исчерпав возможность уменьшения их размеров, мы перешли к созданию многослойных "пирогов". Но и здесь не без проблем - например, отведение тепла или то же число слоев. Тем не менее, не все так плохо, т.к. есть варианты на уровне архитектур процессоров, которые не менее а, порой, даже более эффективны, чем новые технологические нормы. Об этом далее и поговорим...    

Читать далее
Всего голосов 19: ↑12 и ↓7 +5
Просмотры 6.3K
Комментарии 20

В условиях параллелизма обнуление памяти замедляется

Высокая производительность *Программирование *Параллельное программирование *Процессоры
Перевод

Взявшись исследовать некоторые непонятки с производительностью в Chrome, я обнаружил, что Microsoft распараллелили обнуление памяти, и иногда работа из-за этого сильно замедляется. В Windows 11 такое замедление можно частично побороть, но в последних версиях Windows Server — где этот фактор наиболее важен — баг живее всех живых.

Но есть и хорошие новости: по-видимому, проблема актуальна только на тех машинах, где много процессоров. Под «много» я понимаю «96 или более». Поэтому вашего ноутбука проблема не касается. И даже 96-процессорные машины могут сталкиваться с ней не так часто. Но я нашел и другие факторы, из-за которых может быть спровоцирована такая неэффективность, если сложится подходящая ситуация — и обомлел. Вы бы посмотрели, как разбазаривается мощность ЦП. 

Окей – давайте перейдем к деталям.

Читать далее
Всего голосов 34: ↑34 и ↓0 +34
Просмотры 5.5K
Комментарии 4

Go sync/singleflight: устранение дублирования идентичных запросов

Программирование *Go *Параллельное программирование *
Из песочницы
Перевод

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

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

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

Читать далее
Всего голосов 6: ↑5 и ↓1 +4
Просмотры 2.4K
Комментарии 0

Портирование CUDA проекта на Intel oneAPI DPC++

Математика *Параллельное программирование *
Из песочницы

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

Читать далее
Всего голосов 10: ↑10 и ↓0 +10
Просмотры 1.8K
Комментарии 3

Многопоточный Python на примерах: избавляемся от дедлоков

Блог компании Ozon Tech Python *Программирование *Параллельное программирование *
Tutorial

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

Разблокировать
Всего голосов 31: ↑31 и ↓0 +31
Просмотры 9.4K
Комментарии 14

Во что вам обойдется конкурентная обработка. Иерархия проблем

Высокая производительность *C++ *Системное программирование *Алгоритмы *Параллельное программирование *
Перевод

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

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

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

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

Читать далее
Всего голосов 30: ↑28 и ↓2 +26
Просмотры 4.5K
Комментарии 5

Простое руководство по атомарности в C++

Блог компании Издательский дом «Питер» Программирование *C++ *Assembler *Параллельное программирование *
Перевод

Часто возникает путаница с тем, что же понимается в компьютерных науках под «атомарностью». Как правило, атомарность – это свойство процесса, означающее, что он совершается за один шаг или операцию. Но в языке C++  атомарность определяется гораздо более специфичным образом. На самом деле, при использовании std::atomic  с классами и типами еще не гарантируется, что весь код будет подлинно атомарным. Хотя, атомарные типы и входят в состав языка C++, сами атомарные операции должны поддерживаться на уровне того аппаратного обеспечения, на котором работает программа. Эта статья – простое руководство, помогающее понять, что же представляет собой атомарность в C++.

Читать далее
Всего голосов 10: ↑8 и ↓2 +6
Просмотры 5.9K
Комментарии 10

О чём расскажут на Hydra: параллельность и распределённость от введения до хардкора

Блог компании JUG Ru Group Параллельное программирование *Конференции Распределённые системы *

У конференции Hydra в этом году кое-что меняется: кроме двух онлайн-дней, будет ещё и офлайн-день в Петербурге, позволяющий по-настоящему собраться вместе и как следует пообщаться.

И если обычно программа Hydra делилась на два больших блока «concurrency» и «distributed», то в этом году получился ещё и третий: про «внутренности» баз данных.

Но главное остаётся прежним:

— Конференция посвящена разработке параллельных и распределенных систем

— На ней сходятся вместе IT-индустрия и академический мир (тут можно познакомиться и со свежими теоретическими результатами, и с «историями из продакшна»)

— Доклады на английском

О чём именно расскажут в этот раз? Хотя на сайте описания докладов на английском, для хабрачитателей перевели их на русский.

Читать далее
Всего голосов 9: ↑9 и ↓0 +9
Просмотры 1.8K
Комментарии 1

Решение проблемы в управлении конкурентными вычислениями

Блог компании JUG Ru Group Параллельное программирование *Конференции Распределённые системы *
Перевод

От переводчиков. Эту коротенькую статью Дейкстры, которой уже 57 лет, Лесли Лампорт назвал «работой, которая начала всю область конкурентных и распределенных алгоритмов». Но на Хабре её до сих пор вроде бы не переводили. Поскольку мы скоро проведём конференцию Hydra, которая посвящена именно этой области, решили восполнить этот пробел. Кстати, как думаете, как лучше переводить на русский слово concurrent? Мы выбрали вариант «конкурентный», но консенсуса тут вроде бы нет.

Эдсгер В. Дейкстра
Технический университет Эйндховена, Нидерланды

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

Читать далее
Всего голосов 14: ↑14 и ↓0 +14
Просмотры 2.9K
Комментарии 10

Наблюдение за выполнением конкурирующих задач в Go и Rust

Go *Параллельное программирование *Rust *
Из песочницы

Эта статья представляет собой что-то вроде курсовой работы, которую автор не поленился сделать, изучая одновременно Go и Rust. Сильной стороной обоих языков программирования считается удачно реализованная поддержка конкурентности, во всяком случае, редкий обозреватель обходит эту возможность вниманием. Прочитав несколько довольно подробных теоретических описаний и руководств по разработке приложений с конкурентностью на языках Go и Rust, я решил дополнить их несложным количественным экспериментом и поделиться его результатами.  

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

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

Читать далее
Всего голосов 19: ↑16 и ↓3 +13
Просмотры 9.9K
Комментарии 10

Почему мьютексы в Rust реализованы именно так

Программирование *Параллельное программирование *Rust *
Перевод

Я часто слышу от пробующих работать с Rust системных программистов жалобы на мьютексы и особенно на Rust Mutex API. Жалобы обычно выглядят так:

  • Они не хотят, чтобы мьютекс содержал данные, только блокировку.
  • Они не хотят управлять «защитным» значением, разблокирующим мьютекс при сбросе, в частности, они просто хотят вызывать операцию unlock, потому что им кажется, что это более явное действие.

Такие изменения превратили бы Rust mutex API в эквивалент C/Posix mutex API. Однажды я даже видел, как один разработчик пытался использовать Mutex<()> и разные хитрости, чтобы его имитировать.

Однако у такого стремления есть проблема: эти два аспекта Mutex неразрывно связаны друг с другом, а также с гарантиями безопасности Rust в целом — изменение одного из них или обоих откроет возможности для возникновения незаметных багов и повреждений из-за гонок данных.

Использование API мьютексов в стиле C, состоящего из набора косвенно защищаемых данных и из функций lock и unlock было бы опрометчивым в Rust, потому что это позволяет безопасному коду легко вносить ошибки, нарушающие безопасность памяти и вызывающие гонки данных.

Прозвучит спорно, но я утверждаю, что это справедливо и для C. Просто в Rust это более очевидно, поскольку Rust тщательно разделяет понятия «безопасного» кода, в который невозможно внести подобные ошибки, и «небезопасного» кода, в который можно вносить такие ошибки. В C такого разделения нет, и в результате этого использующий мьютексы код на C может тривиальным образом создавать серьёзные баги, которые потенциально можно подвергать эксплойтам.

В этом посте я разберу типичный C mutex API, сравню его с типичным Rust mutex API, и расскажу о том, что произойдёт, если мы изменим Rust API так, чтобы он напоминал C.
Читать дальше →
Всего голосов 60: ↑60 и ↓0 +60
Просмотры 11K
Комментарии 26

Циклы и функционалы в языке R (бесплатный видео курс)

Data Mining *Big Data *Параллельное программирование *R *Data Engineering *

Друзья, рад представить вам свой новый курс "Циклы и функционалы в R". Курс и все сопутствующие материалы к нему распространяются бесплатно, и являются общедоступными. Во время кризиса лучшей инвестицией времени является обучение.

В данной публикации вы найдёте ссылку на курс, подробное описание и программу курса.

Читать далее
Всего голосов 4: ↑2 и ↓2 0
Просмотры 1.2K
Комментарии 0

Вклад авторов