Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.67/9: Рейтинг темы: голосов - 9, средняя оценка - 4.67
0 / 0 / 0
Регистрация: 13.09.2018
Сообщений: 25

Динамический vector<shared_ptr<T>> и многопоточность

12.02.2020, 00:14. Показов 1926. Ответов 13
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Как защитить вызов удалённого из памяти объекта в другом потоке?

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    vector<std::shared_ptr<BulletTwoShape>> vec_bullet;
 
    sf::Thread thrRender(&Game::RenderThread, this);
    sf::Thread thrUpdate(&Game::UpdateThread, this);
 
    void Game::RenderThread()
    {
        wnd->setActive(true);
        while (!is_threads_kill)
        {
            wnd->clear();
            for (auto& tank : vec_tank) tank->Draw();
            for (auto& bullet : vec_bullet) bullet->Draw();
            wnd->display();
        }
    }
 
    void Game::UpdateThread()
    {
        while (!is_threads_kill)
        {
            SystemTime();
            for (auto& tank : vec_tank) tank->Update();
 
            for (auto& bullet = vec_bullet.begin(); bullet != vec_bullet.end();)
            {
                auto& it = *(*bullet);
                it.Update();
                if (it.GetIsEndDistance()) bullet = vec_bullet.erase(bullet);
                else bullet++;
            }
        }
    }
0
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
12.02.2020, 00:14
Ответы с готовыми решениями:

shared_ptr и vector
почему в vector нельзя засунуть shared_ptr ? ...

Динамический массив на shared_ptr
Был динамический массив с выделением памяти через new...Стала переделывать через shared_ptr (задание такое) и возникла куча вопросов и...

Собственный динамический массив (аналог std::vector)
Вот возникла идея написать что-то на подобии вектора, но возникла проблема что не так с kallocSize? Почему компилятор ее не пропускает? А...

13
1741 / 913 / 480
Регистрация: 05.12.2013
Сообщений: 3,074
12.02.2020, 00:20
Цитата Сообщение от A553M613R Посмотреть сообщение
Как защитить вызов удалённого из памяти объекта в другом потоке?
Лучше не удалять, а добавить у объектов поле isLive и отрисовывать только тех у кого оно true
1
0 / 0 / 0
Регистрация: 13.09.2018
Сообщений: 25
12.02.2020, 00:23  [ТС]
Ну тогда контейнер будет раздуваться с каждым выстрелом.
Всё же должен быть способ как отследить удалён ли объект, и если удалён то игнорировать его полностью.
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
12.02.2020, 10:00
Цитата Сообщение от A553M613R Посмотреть сообщение
Всё же должен быть способ как отследить удалён ли объект, и если удалён то игнорировать его полностью.
Здесь тебе надо синхронизировать доступ к vec_bullet
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
std::mutex vec_bullet_mx;
vector<std::shared_ptr<BulletTwoShape>> vec_bullet;
 
sf::Thread thrRender(&Game::RenderThread, this);
sf::Thread thrUpdate(&Game::UpdateThread, this);
 
void Game::RenderThread()
{
    wnd->setActive(true);
 
    vector<std::shared_ptr<BulletTwoShape>> bullets;
    {
        std::lock_guard lock(vec_bullet_mx);
        bullets = vec_bullet;
    }
 
    while (!is_threads_kill)
    {
        wnd->clear();
        for (auto& tank : vec_tank) tank->Draw();
        for (auto& bullet : bullets) bullet->Draw();
        wnd->display();
    }
}
 
void Game::UpdateThread()
{
    while (!is_threads_kill)
    {
        SystemTime();
        for (auto& tank : vec_tank) tank->Update();
 
        std::lock_guard lock(vec_bullet_mx);
        for (auto& bullet = vec_bullet.begin(); bullet != vec_bullet.end();)
        {
            auto& it = *(*bullet);
            it.Update();
            if (it.GetIsEndDistance()) bullet = vec_bullet.erase(bullet);
            else bullet++;
        }
    }
}
0
Эксперт С++
 Аватар для hoggy
8973 / 4319 / 960
Регистрация: 15.11.2014
Сообщений: 9,760
12.02.2020, 12:33
Цитата Сообщение от oleg-m1973 Посмотреть сообщение
Здесь тебе надо синхронизировать доступ к vec_bullet
потеряется перфоманс от многопоточности.

A553M613R,
буквально на днях решал подобную задачу.

на самом деле у тебя есть ровно 3 пути решения:

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

2.
использовать lock-free (это предмет особой олимпиады)
увлекательный мир сложно-замороченных вещей.

3.
lock-free для гуманитариев)))

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


идея очень простая:
- захват свободного мутекса почти бесплатен.
- ожидание занятого мутекса - дорого.

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

коллизии - нежелательны.
они снижают эффективность работы.

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

если сама по себе вероятность возникновения коллизий незначительная,
тогда можно считать, что алгоритм вполне себе эффективен
и без всех этих вундер-вафель



в моём случае у меня был банк данных: map<login, activities>

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

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

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


что сделал я:

разбил общий банк памяти на 100 частей в виде двух-мерного массива: data[10][10]
где data - это map<login_id, activities>

логин юзера представляет собой обычное число.
например: 424968, 520082, 535877

две последние цифры в числе логина - это координаты ячейки в двухмерном массиве,
где хранится ключ данного логина.

например, логин 424968:
C++
1
2
auto& map = data[6][8];
auto& activities = map[424968];
в результате этой простейшей манипуляции вероятность коллизии уменьшилась в 100 раз.
хотя достигается она очень простым способом.

далее я просто создал отдельный поток,
который неспеша, медленно, не более чем по 25 штук логинов за раз (много и не нужно),
с 5 секундными интервалами времени пробегается по всем ячейками массива,
и проверяет: если активность слишком старая, тогда удаляет запись.

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

конечно, что бы удалить запись из контейнера, его необходимо лочить.

однако, одно дело лочить сразу всё, и совсем другое - только 1/100 часть данных.
логины из других ячеек могут обрабатываться параллельно без задержек.
а удаление данных из маленького блока относительно быстро.
можно спинлоком лочить, а не мутексом.


итого:
- разбивай большой банк данных на множество более маленьких блоков.

- активно пишушие треды не должны удалять ключи,
а только помечать их как неактуальные

- непосредственным удалением ключей занимается отдельный фоновый поток "мусоровозка"
(привет, garbage collector!)

- PROFIT ???
3
Just Do It!
 Аватар для XLAT
4202 / 2660 / 654
Регистрация: 23.09.2014
Сообщений: 9,005
Записей в блоге: 3
12.02.2020, 14:37
Цитата Сообщение от A553M613R Посмотреть сообщение
Как защитить вызов удалённого из памяти объекта в другом потоке?
(на правах идеи)
без заумностей, простейшая синхронизация:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
vector<std::shared_ptr<BulletTwoShape>> vec_bullet;
 
sf::Thread thrRender(&Game::RenderThread, this);
sf::Thread thrUpdate(&Game::UpdateThread, this);
 
bool Synch = false;
 
void Game::RenderThread()
{   wnd->setActive(true);
    while (!is_threads_kill)
    {   wnd->clear();
        for (auto& tank : vec_tank) tank->Draw();
    
        
        for (auto& bullet : vec_bullet)
        {   if(Synch) break;   ///пока здесь криво, но зато без краша :D
            bullet->Draw();
        }
        Synch = false;
        
        wnd->display();
    }
}
 
void Game::UpdateThread()
{   while (!is_threads_kill)
    {   SystemTime();
        for (auto& tank : vec_tank) tank->Update();
 
        for (auto& bullet = vec_bullet.begin(); bullet != vec_bullet.end();)
        {   auto& it = *(*bullet);
            it.Update();
            if (it.GetIsEndDistance())
            {   Synch = true;
                bullet = vec_bullet.erase(bullet);
            }
            
            else bullet++;
        }
    }
}


это первая итерация.
если будет работать, (как критично на недорисовку),
то можно слегка усовершенствовать.
0
6772 / 4565 / 1844
Регистрация: 07.05.2019
Сообщений: 13,726
12.02.2020, 15:20
Цитата Сообщение от XLAT Посмотреть сообщение
(на правах идеи)
Это не будет работать. Обращение к vec_bullet - for (auto& bullet : vec_bullet) - тоже должно быть под блокировкой, синхронизироваться с erase , это во-первых.
Второе - если во время вызова bullet->Draw(); этот элемент будет удалён в UpdateThread, то всё тоже будет плохо
1
0 / 0 / 0
Регистрация: 13.09.2018
Сообщений: 25
12.02.2020, 22:47  [ТС]
hoggy, очень познавательный ответ.
Какой из предложенных вариантов посоветуешь использовать для многопользовательской real-time PvP игры?
0
2784 / 1937 / 570
Регистрация: 05.06.2014
Сообщений: 5,602
12.02.2020, 23:05
Цитата Сообщение от A553M613R Посмотреть сообщение
for (auto& tank : vec_tank) tank->Draw();
Вангую что:
1) Draw - константный метод.
2) Основные расходы времени идут не на чтение tank, а на отрисовку.

Таким образом, просто создайте локальную копию vec_tank, в которой будут локальные копии танчиков. В этом случае вам придется лочить доступ только на время создания локальной копии. А на время отрисовки можно неспеша читать локальную копию, не беспокоясь о синхронизациях.
0
 Аватар для zayats80888
6352 / 3523 / 1428
Регистрация: 07.02.2019
Сообщений: 8,995
12.02.2020, 23:22
Можно так:
Пока рисуются танки, обсчитывать пули.
Пока рисуются пули, обсчитывать танки.
Использовать вектор shared_ptr для короткоживущих объектов, да еще и использовать erase - то еще извращение.
Используй пулы.
Раздели ресурсы, которые используются в совместных расчетах, но не нужны при отрисовке (boundin_box танков, для расчета попадания пуль, например), в отдельном пуле.
0
Неэпический
 Аватар для Croessmah
18146 / 10730 / 2066
Регистрация: 27.09.2012
Сообщений: 27,029
Записей в блоге: 1
13.02.2020, 00:14
A553M613R, вы пытаетесь распараллелить заведомо последовательные операции. Зачем? Ничего кроме проблем и худшей работоспособности вы не получите. Если уж так хотите отрисовку кадра в другом потоке, то, если новый кадр еще не готов, рисуйте старый:

C++
1
2
3
4
5
6
7
8
9
10
11
12
//render thread
while (...) {
   //лочим указатель на "переднюю текстуру" и отрисовываем её в окошке
   DrawTexture(*mFrontTexture);
}
 
//update thread
while (...) {
   //обновляем данные
   //рисуем кадр на "заднюю текстуру" (*mBackTexture)
   //под блокировкой обмениваем указатели на переднюю и заднюю текстуры
}
Сетевую часть можно вынести в отдельный поток.
То есть распараллеливать задачи нужно так, чтобы они не требовали синхронизации, либо чтобы имели минимальные точки соприкосновения. В ином случае вы получите мало крупногранулярных блокировок и потоки будут ждать друг-друга постоянно, либо получите дохреналиард мелкогранулярных блокировок, что тоже ни к чему хорошему не приведет, начиная с снижения производительности и заканчивая неуловимыми багами.
3
Just Do It!
 Аватар для XLAT
4202 / 2660 / 654
Регистрация: 23.09.2014
Сообщений: 9,005
Записей в блоге: 3
13.02.2020, 10:30
Цитата Сообщение от Croessmah Посмотреть сообщение
вы пытаетесь распараллелить заведомо последовательные операции
хм, не очевидно.
массив как единое целое?
а почему не набор из отдельных почти независимых друг от друга элементов?
0
0 / 0 / 0
Регистрация: 13.09.2018
Сообщений: 25
16.02.2020, 02:32  [ТС]
Подумал я несколько дней и решил реализовать это таким образом:
Создать интерфейсы с максимально возможным наименьшим объёмом данных у динамических объектов.
При окончании жизни объекта превращать его в абстрактный класс и удалять их в специально отведённое для этого время.
Например если все элементы равны элементам абстрактного класса.
Таким образом размер динамического контейнера во время обработки останется прежним,
это поможет избежать неявное обращение к удалённому из памяти объекта.

Всем спасибо!
0
 Аватар для lemegeton
4903 / 2696 / 921
Регистрация: 29.11.2010
Сообщений: 5,783
16.02.2020, 06:03
Как-то у вас всё в кучу получилось.

Croessmah правильно сказал. Рендер должен произойти строго после апдейта. Во время рендера стейты объектов меняться не должны.

Попробуйте Data-Oriented Design. Он существенно упростит код и уменьшит количество выстрелов в ногу.

Так же рекомендую посмотреть паттерн Entity-Component-System и любые его вариации.

Ну и конечно же книга Game Programming Patterns. Доступная в сети.
2
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
16.02.2020, 06:03
Помогаю со студенческими работами здесь

На основе контейнера vector построить двумерный динамический массив
Каким образом можно один двумерный массив скопировать в другой?С помощью copy() не получается На основе контейнера vector построить...

Реализовать шаблон класса vector, реализующий динамический массив
Реализовать шаблон класса vector, реализующий динамический массив. Определить 2 объекта класса vector и внешнюю функцию, выполняющую...

Что лучше использовать библиотеку <vector> или динамический массив
Что лучше использовать библиотеку &lt;vector&gt; или динамический массив

Vector как динамический массив
Здравствуйте. Не могу понять, как сделать с помощью контейнера vector, динамический массив. Заранее спасибо

Многопоточность - как создать динамический массив потоков
Привет всем:) Вот решил заняться многопоточность и,как и у всех,возникли проблемы. 1) как создать динамический массив потоков? Unit1 ...


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

Или воспользуйтесь поиском по форуму:
14
Ответ Создать тему
Новые блоги и статьи
SDL3 для Web (WebAssembly): сборка C/C++ проекта из консоли
8Observer8 30.01.2026
Содержание блога Если вы откроете примеры для начинающих на официальном репозитории SDL3 в папке: examples, то вы увидите, что все примеры используют следующие четыре обязательные функции, а. . .
Установка Emscripten SDK (emsdk) и CMake на Windows для сборки C и C++ приложений в WebAssembly (Wasm)
8Observer8 30.01.2026
Чтобы скачать Emscripten SDK (emsdk) необходимо сначало скачать и уставить Git: Install for Windows. Следуйте стандартной процедуре установки Git через установщик. Система контроля версиями Git. . .
Подключение Box2D v3 к SDL3 для Android: физика и отрисовка коллайдеров
8Observer8 29.01.2026
Содержание блога Box2D - это библиотека для 2D физики для анимаций и игр. С её помощью можно определять были ли коллизии между конкретными объектами. Версия v3 была полностью переписана на Си, в. . .
Инструменты COM: Сохранение данный из VARIANT в файл и загрузка из файла в VARIANT
bedvit 28.01.2026
Сохранение базовых типов COM и массивов (одномерных или двухмерных) любой вложенности (деревья) в файл, с возможностью выбора алгоритмов сжатия и шифрования. Часть библиотеки BedvitCOM Использованы. . .
Загрузка PNG с альфа-каналом на SDL3 для Android: с помощью SDL_LoadPNG (без SDL3_image)
8Observer8 28.01.2026
Содержание блога SDL3 имеет собственные средства для загрузки и отображения PNG-файлов с альфа-каналом и базовой работы с ними. В этой инструкции используется функция SDL_LoadPNG(), которая. . .
Загрузка PNG с альфа-каналом на SDL3 для Android: с помощью SDL3_image
8Observer8 27.01.2026
Содержание блога SDL3_image - это библиотека для загрузки и работы с изображениями. Эта пошаговая инструкция покажет, как загрузить и вывести на экран смартфона картинку с альфа-каналом, то есть с. . .
Влияние грибов на сукцессию
anaschu 26.01.2026
Бифуркационные изменения массы гриба происходят тогда, когда мы уменьшаем массу компоста в 10 раз, а скорость прироста биомассы уменьшаем в три раза. Скорость прироста биомассы может уменьшаться за. . .
Воспроизведение звукового файла с помощью SDL3_mixer при касании экрана Android
8Observer8 26.01.2026
Содержание блога SDL3_mixer - это библиотека я для воспроизведения аудио. В отличие от инструкции по добавлению текста код по проигрыванию звука уже содержится в шаблоне примера. Нужно только. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2026, CyberForum.ru