Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск  
 
 
Рейтинг 4.95/56: Рейтинг темы: голосов - 56, средняя оценка - 4.95
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700

Синхронизация доступа к разделяемой памяти

21.02.2017, 12:08. Показов 12125. Ответов 73
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Когда потоки являются дочерними по отношению к процессу тут все просто - объект мьютекса находится в общей памяти и используя этот объект можно делать mutex.lock() определенной секции а при завершении работы mutex.unlock();

А как синхронизировать доступ к данным shared memory между процессами?
Подозреваю что в таком случае мьютекс нужно хранить разделяемой памяти
Но как конкретно это реализуется плохо представляю
Подскажите пожалуйста кто знает
0
cpp_developer
Эксперт
20123 / 5690 / 1417
Регистрация: 09.04.2010
Сообщений: 22,546
Блог
21.02.2017, 12:08
Ответы с готовыми решениями:

Реализация стека строк в разделяемой памяти (MPI)
Написать реализацию стека строк в разделяемой памяти. При запуске программа создает блок разделяемой памяти (если его еще нет) или...

Как сохранить данные контейнера в разделяемой памяти
Ищу быстрый способ обмена данными между процессами. Процессы - одинакового типа т.е одна и та же программа и все процессы на одной машине. ...

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

73
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.02.2017, 11:52
Студворк — интернет-сервис помощи студентам
Цитата Сообщение от sys_beginner Посмотреть сообщение
Думаю компилятор может переставить последовательность выполнения в целях оптимизации, если он думает, что это не нарушит общую логику выполнения
Естественно. С точки зрения одного потока исполнения порядок записи в две переменные не важен. И именно по этим соображениям компилятор их может переставить местами. Ровно по таким же соображениям аппаратура так же может переставить местами эти две записи. Ни компилятор, на аппаратура не знают, что в другом потоке важно, в каком порядке произойдёт запись в две переменные. Для этого и придумали способ, как это явно указать - добавив класс атомарных операций с семантикой, обеспечивающей требуемую упорядоченность. Это влияет как на компилятор, так и на исполнение операций в аппаратуре

Цитата Сообщение от sys_beginner Посмотреть сообщение
Учитывая это я думаю все таки можно сначала писать данные с локом, но на чтении не лочить доступ а просто смотреть флаг, нет?
Если ты хочешь нормально работающий переносимый софт, то нет. Точнее, да, но при условии, что чтения и записи флага делаются специальным образом
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
22.02.2017, 12:03  [ТС]
Цитата Сообщение от Evg Посмотреть сообщение
С точки зрения одного потока исполнения порядок записи в две переменные не важен.
Я думал об этом, но с другой стороны при компиляции же видно что данные пишутся в шаред мемори, использование которого как бы намекает что другие процессы тоже могут использовать эту информацию. Имея на руках такую информацию компилятор должен сделать соответствующий вывод, разве нет?
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.02.2017, 12:12
Цитата Сообщение от sys_beginner Посмотреть сообщение
но с другой стороны при компиляции же видно что данные пишутся в шаред мемори
Каким образом видно?

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

Про то, чем различается реализация различных семантик на пальцах я объяснял здесь: Std::atomic. Реализация свободного от блокировок стека И такие понятия вводятся в язык через более высокоуровневые конструкции (типа класса std::atomic в C++)
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
22.02.2017, 12:37  [ТС]
Цитата Сообщение от Evg Посмотреть сообщение
Каким образом видно?
Тот указатель по которому будет происходить запись будет выделяться из шаред мемори

Цитата Сообщение от Evg Посмотреть сообщение
Т.е. в языке есть просто указатель, но нет таких понятий, как "указатель на память, доступную из других мест"
Ну если так тогда понятно... значит придется лепить лок
Спасибо
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.02.2017, 13:20
Цитата Сообщение от sys_beginner Посмотреть сообщение
Тот указатель по которому будет происходить запись будет выделяться из шаред мемори
Во-первых это видно только в твоём конкретно примере, да и то при условии, что компилятор будет распознавать какие-то функции и классы как сущности, рождающие shared-память. Во-вторых если память разделяемая, то это вовсе не означает, что с ней надо как-то особым образом работать. Особым образом надо работать с объектами синхронизации, а вовсе не с разделяемой памятью. И этот особый способ работы как раз и есть правильное использование программистом семантики атомарного обращения к объекту синхронизации
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
22.02.2017, 13:27  [ТС]
Цитата Сообщение от Evg Посмотреть сообщение
Во-первых это видно только в твоём конкретно примере, да и то при условии, что компилятор будет распознавать какие-то функции и классы как сущности, рождающие shared-память.
Не вижу никаких проблем. По коду же видно - компилятор соответственно может вполне распознать такие вызовы и тем самым позволить разработчику сократить количество локов для конкретно того случая который я описал
Странно что такие гарантии не даются
0
Неэпический
 Аватар для Croessmah
18149 / 10731 / 2067
Регистрация: 27.09.2012
Сообщений: 27,038
Записей в блоге: 1
22.02.2017, 13:40
Цитата Сообщение от sys_beginner Посмотреть сообщение
По коду же видно
Где? Как? А если у меня обертка в библиотеке?
1
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.02.2017, 13:59
Цитата Сообщение от sys_beginner Посмотреть сообщение
По коду же видно
Как в данном примере компилятор может понять, что здесь идёт работа с разделяемой памятью

C
void foo (int *p)
{
  *p = 0;
}
И даже если бы это было возможно, то тут возникает миллион других проблем. Если ты читал ссылку из поста #43, то знаешь, что для того, чтобы актуализировать значение семафора нужно проделать очень много всяких действий по синхронизации памяти (кэшей) между процессорами. А теперь представь себе, что было бы, если бы КАЖДОЕ обращение в разделяемую память обладало бы такими свойствами. Производительность упала бы в десятки (если не в сотни) раз
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
22.02.2017, 16:10  [ТС]
Цитата Сообщение от Croessmah Посмотреть сообщение
А если у меня обертка в библиотеке?
Верно. Об этом я не подумал

Evg,
Я уже допетрил)) Спасибо что объяснил
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.02.2017, 16:29
Цитата Сообщение от Evg Посмотреть сообщение
Для ptread_mutex_lock завтра попробую посмотреть, что в glibc'ях сделано
Грубо говоря, схема такая. Допустим, мьютекс был свободен, его захватил поток1. Далее сюда же ломится поток2 и видит, что мьютекс занят. Он прописывает себя в очередь и засыпает через специальный системный вызов futex (т.е. отдаёт ядру приказ "усыпи меня"). Допустим, сюда же ломится поток3. Аналогичным образом он прописывает себя в очередь и засыпает

В конечном итоге поток1 вызовет интерфейс "освободить мьютекс". Внутри этой функции проверяется очередь и первый из очереди (а нашем случае это поток2) будет разбужен через тот же самый системный вызов futex (т.е. поток1 отдаёт ядру приказ "разбудить поток2"). При этом сам мьютекс останется в состоянии "занято". После чего поток1 выйдет из этого интерфейса и продолжит работу. Разбуженный поток2 выполняет действия в критической секции и вызовет интерфейс "освободить мьютекс". В процессе этого ядру будет отдан приказ "разбудить поток3", а сам мьютекс по прежнему останется в состоянии "занято". Поток2 продолжает свою работу, а поток 3 просыпается и так далее. Когда в конечном итоге поток3 вызовет интерфейс "освободить мьютекс", то очередь опустеет, после чего будить будет некого. Тогда мьютекс переведётся в состоянии "свободно"
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
22.02.2017, 16:39  [ТС]
Evg,
Интересно Но lock-free который мы обсуждали прежде по любому выигрывает у этого мьютекса
1. Нет системных вызовов
2. Если захват недоступен можно выполнять другую полезную работу
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.02.2017, 16:49
Термином lock-free называют несколько другую вещь (или даже свойство)

Цитата Сообщение от sys_beginner Посмотреть сообщение
2. Если захват недоступен можно выполнять другую полезную работу
Есть две разные операции - lock и trylock. То, что ты пишешь - это операция trylock. Т.е. либо захватить, либо отвалить. У mutex'ов она работает ровно так же. А то, что я описывал в посте #50 - это операция lock (которой в вашем обсуждении не было вообще). Другими словами, ваша реализация неполноценная (с точки зрения универсальности) ввиду отсутствия операции lock

Добавлено через 1 минуту
Точнее, в вашей реализации была операция lock, она реализована в виде жужжания. Я уже писал ранее, что она имеет ограниченое применение. То, что вы реализовали, по сути является понятием "spinlock"
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
22.02.2017, 17:14  [ТС]
Цитата Сообщение от Evg Посмотреть сообщение
Термином lock-free называют несколько другую вещь (или даже свойство)
Как это? Из википедии
Неблокирующая синхронизация — подход в параллельном программировании на симметрично-многопроцессорных системах, в котором принят отказ от традиционных примитивов блокировки, таких, как семафоры, мьютексы и события. Разделение доступа между потоками идёт за счёт атомарных операций и специальных, разработанных под конкретную задачу, механизмов блокировки.
Атомарные операции есть? Есть. В случае с циклом - блокировка тоже есть.

Цитата Сообщение от Evg Посмотреть сообщение
То, что ты пишешь - это операция trylock.
Если без цикла то да, но я предыдущем посте говорил именно в контексте синхронизации, c циклом.

Lock-free насколько мне известно это просто принцип синхронизации доступа на основе атомарных операций, обычно это CAS. Не знаю, может существуют и другие подобные операции
0
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
22.02.2017, 19:11
Нет. Lock-free - это свойство атомарного примитива, говорящее о том, что ВНУТРИ СЕБЯ примитив реализован без использования дополнительных объектов синхронизации, а использует исключительно аппаратную поддержку. Вы использовали метод (примитив) compare_exchange_weak, который сам по себе в общем случае может быть реализован как угодно, в том числе содержать мьютексы или что-то подобное (т.е. имитирует атомарность). Если такой примитив (compare_exchange_weak или любой другой) реализован аппаратной операцией, то говорят, что это Lock-free примитив. Т.е. при любых внешних условиях примитив либо гарантированно выполнится, либо гарантированно не выполнится, никаких промежуточных состояний быть не может. Но вот если внутри реализации использован мьютекс, то тут возможно промежуточное состояние. Т.е. примитив при своей работе захватил мьютекс (я говорю именно о внутреннем мьютексе реализации примитива, а не о пользовательском мьютексе, который реализуется через примитив), а потом по каким-то причинам процесс сдох, повис или что-то подобное. Это приведёт к Dead-lock'у. Для примитивов, обладающих свойством Lock-free, Dead-lock невозможен
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
23.02.2017, 10:12  [ТС]
Renji,
Синхронизация доступа к разделяемой памяти
Представим ситуацию из следующего примера:
1. Процесс пробился через флаг и пошел работать сделав состояние флага снова "занято"
2. Этот самый процесс сдох по каким то причинам не добравшись до memory_order_release
3. Состояние флага остается "занято"
Выходит при таком раскладе событий этот участок больше ни кем не может быть выполнен?
Как это называется и разруливается? Дедлок насколько я понимаю это когда два или более задания держат блокировку на ресурсы которые нужны и одному, а этот самый "одному" держит блокировку на тот ресурс который нужен другому

Вопрос навеян постом #54 (там где про дедлоки)

Добавлено через 14 минут
Цитата Сообщение от sys_beginner Посмотреть сообщение
Дедлок насколько я понимаю это когда два или более задания держат блокировку на ресурсы которые нужны и одному, а этот самый "одному" держит блокировку на тот ресурс который нужен другому
Либо когда один поток дважды захватывает не рекурсивный примитив

Добавлено через 32 минуты
Renji,
Вот еще что интересно. Что бы этот флаг был виден в процессах его нужно сохранить в шаред мемори
но тут я не вижу соответствующего конструктора который принимает флаг
http://en.cppreference.com/w/c... tomic_flag

Однако инициализация происходит успешно если написать так:
C++
1
std::atomic_flag lock = ATOMIC_FLAG_INIT;
илии так
C++
1
2
void *ptr = operator new(32);//представим что это шаред мемори 
std::atomic_flag *lock = new (ptr)std::atomic_flag(ATOMIC_FLAG_INIT);
0
2784 / 1937 / 570
Регистрация: 05.06.2014
Сообщений: 5,602
23.02.2017, 11:59
Цитата Сообщение от sys_beginner Посмотреть сообщение
2. Этот самый процесс сдох по каким то причинам не добравшись до memory_order_release
Скорее всего, оставив данные в некорректном состоянии (половина данных новая, половина старая). Так что лезть в эти данные в любом случае нельзя.
Цитата Сообщение от sys_beginner Посмотреть сообщение
Как это называется и разруливается?
Очень просто разруливается.
1) Процесс подключается к серверу базы данных и инициирует начало транзакции.
2) Спустя, скажем, десять секунд сервер решает "чего-то долго ждем, наверно, процесс сдох" и откатывает транзакцию.
Но нужно пилить поддержку транзакций и, собственно, сервер базы данных. И тут уже в полный рост встает вопрос зачем все это пилить, вместо того чтобы взять готовую СУБД.

Добавлено через 1 минуту
Цитата Сообщение от sys_beginner Посмотреть сообщение
Однако инициализация происходит успешно если написать так:
"In addition, std::atomic_flag can be value-initialized to clear state with the expression ATOMIC_FLAG_INIT"
1
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
23.02.2017, 12:05  [ТС]
Renji,
Не очень понял как СУБД связана с данной проблемой. Или это был просто пример?

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

Добавлено через 1 минуту
Цитата Сообщение от Renji Посмотреть сообщение
"In addition, std::atomic_flag can be value-initialized to clear state with the expression ATOMIC_FLAG_INIT"
Значит все таки можно? Но в описании самого конструктора там где указываются шаблоны реализаций такого нету... странно
0
2784 / 1937 / 570
Регистрация: 05.06.2014
Сообщений: 5,602
23.02.2017, 12:10
Цитата Сообщение от sys_beginner Посмотреть сообщение
Не очень понял как СУБД связана с данной проблемой. Или это был просто пример?
Связана так, что то что вы пытаетесь запилить - подозрительно похоже на СУБД. Поиск данных по ключу, синхронизация доступа в многопоточной среде, транзакции... В чем разница то, в отсутствии языка SQL запросов?
Цитата Сообщение от sys_beginner Посмотреть сообщение
Пока думаю решить так: запускать фоновый процесс который проверяет жив ли процесс который в последний раз захватил блокировку, если время захвата более чем нас устраивает, в этом процессе сделать release флага и дать возможность продолжить работу другим потокам и откатить изменения сделанные уже мертвым процессом в общей памяти
Чтобы фоновый процесс мог делать откаты, все операции с общей памятью должны идти через него. Иначе как он будет знать что именно откатывать? Ну, то есть, сервер СУБД и получится.
Цитата Сообщение от sys_beginner Посмотреть сообщение
Значит все таки можно?
Можно. Через ATOMIC_FLAG_INIT онли.
1
Evg
Эксперт CАвтор FAQ
 Аватар для Evg
21281 / 8305 / 637
Регистрация: 30.03.2009
Сообщений: 22,660
Записей в блоге: 30
23.02.2017, 12:12
Цитата Сообщение от sys_beginner Посмотреть сообщение
Как это называется и разруливается?
Единственный нормальный способ разрулить такую ситуацию - это писать программу так, чтобы такой ситуации не возникало. Естественно, в реальной жизни это недостижимо, а потому применяют технологии из разряда тех, о чём писал Renji - над системой появляется дополнительная программа-диспетчер, которая надзирает за всем процессом. К слову говоря, эта программа-диспетчер тоже может по каким-то причинам сдохнуть, а потому проблему на 100% разрулить невозможно

Цитата Сообщение от sys_beginner Посмотреть сообщение
Вопрос навеян постом #54 (там где про дедлоки)
Там ситуация отличается от того, что ты пишешь.

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

Моя ситуация. Кто-то начал захватывать мьютекс (который является non-lock-free) и сдох. Т.е., грубо говоря, речь идёт о мьютексе, работоспособность которого изнутри обеспечивает другой мьютекс. В этом случае в непонятном состоянии находится мьютекс - непонятно, он открыт или закрыт. Т.е. другие процессы, которые хотят захватить мьютекс, даже не в состоянии понять, свободен мьютекс, или занят

Вот примерно такая разница между примтивами lock-free и non-lock-free. Примитив - это грубо говоря операции compare_exchange_weak, посредством которых реализуется мьютекс. Lock-free примитив compare_exchange_weak гарантирует, что в процессе его работы значение переменной будет либо гарантированно старым, либо гарантированно новым. В то время как не-lock-free примитив такого гарантировать не сможет. В случае, если в процесс работы примитива что-то случится, то значение переменной может оказаться в промежуточном неизвестном состоянии
0
901 / 478 / 93
Регистрация: 10.06.2014
Сообщений: 2,700
23.02.2017, 12:15  [ТС]
Цитата Сообщение от Renji Посмотреть сообщение
Связана так, что то что вы пытаетесь запилить - подозрительно похоже на СУБД. Поиск данных по ключу, синхронизация доступа в многопоточной среде, транзакции... В чем разница то, в отсутствии языка SQL запросов?
Ну мне просто весь функционал базы не нужен. Тут даже больше nosql key-value подходят
Цитата Сообщение от Renji Посмотреть сообщение
Чтобы фоновый процесс мог делать откаты, все операции с общей памятью должны идти через него.
Плохо понял что вы имеете ввиду. Разве ему не достаточно иметь доступ к разделяемой памяти?
Цитата Сообщение от Renji Посмотреть сообщение
Иначе как он будет знать что именно откатывать?
Например будет ориентироваться на метаданные из разделяемой памяти

Я почему интересуюсь решением без СУБД... есть ведь lock-free контейнеры которые работают без СУБД. Они же как то справляются только за счет алгоритма... Вот мне интересно как это реализуется. Мне пока нужно просто понять идею что бы легко читать сложные исходники
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
raxper
Эксперт
30234 / 6612 / 1498
Регистрация: 28.12.2010
Сообщений: 21,154
Блог
23.02.2017, 12:15

Аська на основе разделяемой памяти
Прогреры и хакеры и им подобные... обращаюсь с особой просьбой: есть мысли по поводу перессылки сообщений через разделяемую память между...

Запись и считывание разделяемой памяти
Всем доброго времени суток. Столкнулся с одной проблемой. Есть две программы: 1 создаёт разделяемую память и записывает туда данные, а...

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

Считать структуру из разделяемой памяти
Добрый день! Есть разделяемый файл. В приложении А в файл запихиваю структуру. В приложении Б считываю эту структуру из файла. Выдается...

Сделать массив из 10 int в разделяемой памяти
Хочу сделать массив из 10 int в разделяемой памяти. Доступ из разных процессов от fork(). Так понимаю, 19-20 строки должны сказать...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
60
Ответ Создать тему
Новые блоги и статьи
сукцессия 29. Переход от одних деревьев на другие делать более или менее вероятностным?
anaschu 12.07.2026
Насколько смена типов микоризы — исключительное событие в двухвековой сукцессии? Оценка вероятности в пространстве параметров В текущей версии модели успешно реализован ключевой механизм. . .
сукцессия 27. Думаю, как переделывать уже написанную статью с планами на сукцессию.
anaschu 12.07.2026
Анализ соответствия модели требованиям Реализованные компоненты: Механизм закисления почвы через протонную помпу Конкуренция между типами микориз pH как триггер сукцессии C/ P соотношение. . .
Сукцессия 26. Мат модель создана.
anaschu 12.07.2026
Модель смены растительных сукцессий посредством управления грибами работает внутри небольшой ячейки почвы, восстанавливающейся после пожара, где ненадолго бывшее царство хвойных снова захватили. . .
Решил проблему с ошибкой пагинации сообщений с сервера на алгоритме обхода дерева "Эстафета хвоста".
Hrethgir 12.07.2026
Проблема была в том, что удалялась именно новая кнопка, а не старая. Ни один ИИ не обнаружил это, а сам я смог только когда с работой стало попроще и когда заставил работать будущее автономное. . .
сукцессия 25. Хронология ошибок
anaschu 12.07.2026
# От 50-тонного гриба до устойчивого леса: хроника ошибок при построении модели вековой сукцессии микоризы ## О чём эта статья В процессе построения ОДУ-модели (система дифференциальных. . .
сукцессия 24. Промежуточное общее описание модели
anaschu 12.07.2026
Хендофф: модель АМ→ЭКМ сукцессии микоризы (ризосфера, 50 лет) Содержание проекта Симуляция вековой (50 лет) экологической сукцессии в почве леса Основные участники: АМ-гриб, ЭКМ-гриб,. . .
сукцессия 23. Более физиологичная физиология, более экологичная экология, более диффурные диффуры.
anaschu 12.07.2026
Что реально нашли и починили за эти 5 часов Правило Линдемана (КПД конверсии сахара в тело, kEff) — раньше 100% полученного углерода шло прямо в биомассу гриба; теперь только kEff=0. 5 (после. . .
сукцессия 22. От артефактов к физиологии: калибровка агентной модели грибной сукцессии для воспроизведения сезонной динамики и pH-плато
anaschu 11.07.2026
Аннотация В данной работе представлена калибровка агентной модели динамики грибных сообществ (fungal-succession), направленная на устранение нефизичных артефактов (коллапс биомассы, мгновенное. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru