Удаление рабочего элемента многопоточного загрузчика текстур C++ из основного потока

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

Запрос загрузчика текстуры представляет собой простую структуру с необработанным указателем на объект, который получит текстуру:

struct TextureLoaderRequest
{
    std::string mFilename;
    ContentViewer *mContentViewer;
};

Фактический объект текстуры, содержащийся в ContentViewer, защищен мьютексом и некоторыми атомарными логическими значениями (также содержащимися в ContentViewer):

std::atomic<bool>               mIsLoaded;
std::atomic<bool>               mIsVisible;
std::mutex                      mImageMutex;

Тогда процедуры доступа к текстуре выглядят следующим образом:

void ContentViewer::setTexture(ci::gl::TextureRef texture)
{
    std::lock_guard<std::mutex> guard(mImageMutex);
    mImage = texture;
}

ci::gl::TextureRef ContentViewer::getTexture()
{
    std::lock_guard<std::mutex> guard(mImageMutex);
    if (mIsVisible)
    {
        if (mImage != nullptr)
        {
            mIsLoaded = true;
            return mImage;
        }
        mIsLoaded = false;
    }
    return nullptr;
}

Загрузчик текстур может получать множество запросов на загрузку текстур от основного потока за один раз, а затем работает через загрузку очереди и назначение текстур средству просмотра контента, указанному в сообщении с запросом на загрузку текстуры.

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

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

Спасибо - Лейте


person Laythe    schedule 05.02.2017    source источник
comment
будет ли полезно использовать std::shared_ptr?   -  person xaxxon    schedule 05.02.2017
comment
Я изменил ваш вопрос для opengl-es. (Тег opengl означает рабочий стол-gl). Если вместо этого вы имели в виду desktop-gl, не стесняйтесь пометить его этим и удалить тег opengl-es.   -  person BDL    schedule 05.02.2017
comment
Можете ли вы поделиться своей функцией работы с текстурой?   -  person    schedule 05.02.2017
comment
@xaxxon Как здесь можно использовать shared_ptr? Я предполагаю, что запрос текстуры может иметь weak_ptr, а затем поток проверяет weak_ptr, чтобы определить достоверность. Думаю, мне все равно понадобится синхронизация вокруг общих указателей. Мне кажется более правильным использовать необработанные указатели для этого варианта использования, но я не эксперт в использовании общих указателей.   -  person Laythe    schedule 06.02.2017
comment
@farhat latrach Пожалуйста, смотрите ниже   -  person Laythe    schedule 06.02.2017


Ответы (3)


Итак, имхо есть два варианта:

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

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

person JHBonarius    schedule 05.02.2017
comment
Привет, я не могу дождаться выполнения запроса. Этот код предназначен для выполнения операции закрытия, поэтому желаемый эффект должен заключаться в отмене невыполненных запросов. Второй вариант, который вы упомянули, - это моя первая попытка, и я обнаружил, что если я проверю достоверность, а затем использую объект, он может исчезнуть между проверкой и использованием, поэтому произойдет сбой. Я нашел решение, которое я изложу ниже. Спасибо за вашу помощь! - person Laythe; 06.02.2017
comment
В многопоточной системе вы должны заблокировать объект, чтобы предотвратить такие ситуации, когда, например, объект удаляется после проверки на достоверность. Иначе невозможно предотвратить ситуацию, которую вы описываете. поэтому метод удаления, например, также должен блокировать mImageMutex. Ваш вопрос является распространенным в многопоточности. Как указано здесь [ссылка] stackoverflow.com/questions/12455297/ : вы не можете удалить используемый объект. Никакие мьютексы этого не исправят. - person JHBonarius; 06.02.2017

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

Меня все еще интересуют альтернативы/предложения.

Небольшой план темы ниже:

void textureLoaderThreadFn()
{
    log("texture loader thread started");

    while (!mShouldQuit)
    {
        // Wait for texture loader request
        TextureLoaderRequest *textureLoaderRequest = nullptr;
        mTextureRequests->popBack(&textureLoaderRequest);

        // it is possible popBack didnt modify textureLoaderRequest (eg. when cancelled on exit)
        if (textureLoaderRequest != nullptr)
        {
            std::lock_guard<std::mutex> lk(mCancellationListMutex);

            if (std::find(mCancellationList.begin(), mCancellationList.end(), textureLoaderRequest->mFilename) != mCancellationList.end())
            {
                // Cancelled

                // we must reset the isLoading that was set by the main thread,
                // so that the request to load the texture can get put back if need be
                textureLoaderRequest->mContentViewer->mIsLoading = false;

                // remove from cancellation list
                mCancellationList.erase(std::remove(mCancellationList.begin(), mCancellationList.end(), textureLoaderRequest->mFilename), mCancellationList.end());
            }
            else
            {
                // Not cancelled
                <SERVICE TEXTURE REQUEST>
            }

            // dont need this anymore
            delete textureLoaderRequest;
        }
    }
    log("texture loader thread stopped");

    // Empty the queue    
    int count = 0;
    TextureLoaderRequest *textureLoaderRequest = nullptr;
    while (mTextureRequests->tryPopBack(&textureLoaderRequest))
    {
        if (textureLoaderRequest != nullptr)
            delete textureLoaderRequest;
        count++;
    }
    log("texture loader thread purged " + std::to_string(count) + " outstanding texture load requests");
}
person Laythe    schedule 06.02.2017

Я бы посоветовал вам добавить флаг состояния в средство просмотра контента, чтобы различать 3 статуса; запланированные для окрашивания, окрашиваемые и не запланированные для окрашивания. Основной поток должен удалять средство просмотра контента только в том случае, если его состояние запланировано для окрашивания или не запланировано для окрашивания.

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

Изменение статусов и проверка статуса на возможность его удаления всегда должны осуществляться одним и тем же мьютексом; вы можете установить флаг как частный в средстве просмотра контента и использовать два общедоступных метода: 1) void change_status (status) и 2) bool can_delete().

Обе функции должны начинаться с получения одного и того же мьютекса. 1) используется в различных переходах в основном потоке и в потоке обработки текстур, а 2) используется основным потоком перед удалением средства просмотра содержимого, чтобы вернуть значение true, только если статус не окрашивается.

В потоке работы с текстурами перед выходом вы можете удалить последнее окрашенное содержимое, если оно не было удалено основным потоком (как это могло быть при окрашивании статуса).

person Community    schedule 06.02.2017
comment
основной поток не сможет удалить его, так как его статус окрашивается; can_delete вернет false. Затем этот последний, который вы удаляете при выходе из потока текстур... - person ; 06.02.2017
comment
Привет, если основной поток хочет закрыть средство просмотра содержимого до того, как будет выполнен запланированный запрос, единственная схема, которую я придумал до сих пор, - это отметить тот факт, что мы удалили средство просмотра содержимого (отмена, защищенная мьютексом список, который я упоминаю ниже), а затем загрузчик текстур отменяет запрос текстуры на основании адреса назначения запроса, находящегося в списке отмены. Это вариант, который я описал ниже, и, кажется, он работает, но не подвергался стресс-тестированию. Я не уверен, зачем нам нужно защищать атомарный логический флаг с помощью std:mutex? - person Laythe; 06.02.2017
comment
Вы имеете в виду удалить средство просмотра содержимого из самого потока загрузки текстур? - person Laythe; 06.02.2017
comment
Кроме того, основной поток должен удалять средство просмотра содержимого только тогда, когда его состояние запланировано для окрашивания или не запланировано для окрашивания. Что будет делать основной поток в этом случае, встретит ли он средство просмотра содержимого, которое окрашивается (загружается)? Спасибо - person Laythe; 06.02.2017
comment
пропустить и не удаляет его по мере использования. Всегда он должен быть только один в этом статусе. Я предполагаю, что основной поток, решив удалить средство просмотра содержимого, также закроет поток текстуры, поэтому рабочий поток текстуры при выходе удаляет последнее средство просмотра содержимого, которое он использовал, если оно еще не удалено; скажем, вы всегда сохраняете указатель последнего и перед выходом проверяете его. - person ; 06.02.2017
comment
Извините за некоторую расплывчатость. Поток запроса текстуры должен висеть без дела, так как это общий поток загрузки, возможно, обслуживающий другие, поэтому поток текстуры не может удалить средства просмотра содержимого. - person Laythe; 06.02.2017
comment
Если бы в основном цикле потока текстуры он каким-то образом знал, когда можно безопасно удалить средство просмотра содержимого, то я мог бы использовать ту же логику для выполнения загрузки текстуры с игнорированием, но я не понимаю, как это возможно без некоторых своего рода список отмены, который поток текстур должен обрабатывать при каждом сканировании потока. - person Laythe; 06.02.2017