Russian Qt Forum
Ноябрь 26, 2024, 12:11 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3 ... 5   Вниз
  Печать  
Автор Тема: Потокобезопасный кэш  (Прочитано 30726 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Декабрь 27, 2010, 18:48 »

Добрый день

Ну если просто "потокобезопасно" (звучит ужасно) - то достаточно просто поставить лок на все обращения к кэшу. Но все-таки: как сделать по уму? 

Спасибо
Записан
kuzulis
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2812


Просмотр профиля
« Ответ #1 : Декабрь 27, 2010, 19:33 »

Зависит, наверное, от кол-ва пишущих/читающих из кеша потоков. ИМХО, сделать что-то универсальное без мьютексов врядли получится, нужно исходить из конкретного случая.
 Да и что под кешем имеешь ввиду?
Записан

ArchLinux x86_64 / Win10 64 bit
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #2 : Декабрь 27, 2010, 19:44 »

Зависит, наверное, от кол-ва пишущих/читающих из кеша потоков. ИМХО, сделать что-то универсальное без мьютексов врядли получится, нужно исходить из конкретного случая.
 Да и что под кешем имеешь ввиду?
Да что имею - то и введу. Значение взято из кэша и пользуется - в этот момент др. нитка вытесняет его из кэша. Что бум делать?
Записан
JamS007
Гость
« Ответ #3 : Декабрь 27, 2010, 19:53 »

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

Хотя, в идеале, можно разграничить информацию на уровне потоков, так, чтобы каждый поток занимался информацией своего типа. В таком случае выше описанный кэш позволил бы почти без гонок предоставить доступ к информации.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #4 : Декабрь 27, 2010, 20:00 »

Как вариант - можно создать кэш нескольких уровней, каждый их которых будет содержать специфичную информацию. Таким образом, разбив по определенному признаку всю информацию,
...
Хотя, в идеале, можно разграничить информацию на уровне потоков, так, чтобы каждый поток занимался информацией своего типа.
Hmmm... too much fantasy  Улыбающийся
Записан
JamS007
Гость
« Ответ #5 : Декабрь 27, 2010, 20:20 »

Цитировать
Hmmm... too much fantasy

Вы не поняли сути или думаете, что это непрактично?
Опишите свою задачу более детально, подумаем вместе.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #6 : Декабрь 27, 2010, 20:31 »

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

Все хорошо но как делать это с 2 и более нитками?
Записан
Akon
Гость
« Ответ #7 : Декабрь 27, 2010, 20:43 »

Зависит, наверное, от кол-ва пишущих/читающих из кеша потоков. ИМХО, сделать что-то универсальное без мьютексов врядли получится, нужно исходить из конкретного случая.
 Да и что под кешем имеешь ввиду?
Да что имею - то и введу. Значение взято из кэша и пользуется - в этот момент др. нитка вытесняет его из кэша. Что бум делать?

QSharedPointer<const ImagePart>. Сам QSharedPointer потокобезопасен (но не указываемые данные!). Когда одна нитка берет данные из кэша, то эти данные можно спокойно удалять из кэша в другой нитке. Т.о. всегда можно поддержать размер кэша не больше заданного значения, в то же время используемые данные, уже не находящиеся в кэше, автоматически уничтожатся QSharedPointer'ом как только в них не станет необходимости. Сами QSharedPointer<const ImagePart>'ы будут организованы в какой либо контейнер, доступ к которому, как говорилось выше, должен быть синхронизирован.
Записан
JamS007
Гость
« Ответ #8 : Декабрь 27, 2010, 20:49 »

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

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

Код
C++ (Qt)
QHash<Paper*,bool>

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

Выгоды данного подхода: вся работа с кэшем осуществляется в одном потоке, соответственно - ни одного мъютекса, а значит - меньше тормозов. Во вторых все потоки, которые работают с изображением работают с разными его частями, не мешая друг другу и без необходимости синхронизации работ.
« Последнее редактирование: Декабрь 27, 2010, 20:51 от JamS007 » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #9 : Декабрь 27, 2010, 20:56 »

QSharedPointer<const ImagePart>. Сам QSharedPointer потокобезопасен (но не указываемые данные!). Когда одна нитка берет данные из кэша, то эти данные можно спокойно удалять из кэша в другой нитке. Т.о. всегда можно поддержать размер кэша не больше заданного значения, в то же время используемые данные, уже не находящиеся в кэше, автоматически уничтожатся QSharedPointer'ом как только в них не станет необходимости. Сами QSharedPointer<const ImagePart>'ы будут организованы в какой либо контейнер, доступ к которому, как говорилось выше, должен быть синхронизирован.
Если я чего-то не понимаю - поясните, но я так вижу что кэш сам/тоже "владеет" данными. Предположим одна нитка обратилась за данными - попользовалась, освободила. Др. обращений к той же странице (покв) нет. Так что, удалить данные? Что это за кзш если он "ничего не держит"?
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Декабрь 27, 2010, 21:06 »

Если нескольким потокам нужно иметь доступ к кэшу изображения, но к разным его частям...
В реальной задаче невозможно предсказать к каким страницам будет обращение и сколько раз. Напр. вполне возможно что огромное изображение грузится но реально из него используется лишь несколько пикселей (для этого и городится огород со страницами)
Записан
JamS007
Гость
« Ответ #11 : Декабрь 27, 2010, 21:12 »

Цитировать
В реальной задаче невозможно предсказать к каким страницам будет обращение и сколько раз.

А зачем это предсказывать? Потоку нужна страница - он ее запросил, сделал все, что нужно, вернул управление в кэш. Теперь кэш может раздавать ее другим потокам, или опять этому, это уже дело кэша, что с ней делать дальше.

Каждый из потоков может запросить сколько угодно страниц, и с какой угодно частотой. На то и создается кэш.
Записан
Akon
Гость
« Ответ #12 : Декабрь 27, 2010, 21:21 »

QSharedPointer<const ImagePart>. Сам QSharedPointer потокобезопасен (но не указываемые данные!). Когда одна нитка берет данные из кэша, то эти данные можно спокойно удалять из кэша в другой нитке. Т.о. всегда можно поддержать размер кэша не больше заданного значения, в то же время используемые данные, уже не находящиеся в кэше, автоматически уничтожатся QSharedPointer'ом как только в них не станет необходимости. Сами QSharedPointer<const ImagePart>'ы будут организованы в какой либо контейнер, доступ к которому, как говорилось выше, должен быть синхронизирован.
Если я чего-то не понимаю - поясните, но я так вижу что кэш сам/тоже "владеет" данными. Предположим одна нитка обратилась за данными - попользовалась, освободила. Др. обращений к той же странице (покв) нет. Так что, удалить данные? Что это за кзш если он "ничего не держит"?

Разумеется, кэш владеет данными.
Вы же указали
Цитировать
Значение взято из кэша и пользуется - в этот момент др. нитка вытесняет его из кэша. Что бум делать?
QSharedPointer все сделает в лучшем виде Улыбающийся.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #13 : Декабрь 28, 2010, 09:04 »

QSharedPointer все сделает в лучшем виде Улыбающийся.
Ладно, давайте я попробую набросать с QSharedPointer а Вы меня где надо поправите

Код
C++ (Qt)
typedef QSharedPointer <unsigned char *>  TSharedPtr;
typedef QWeakPointer <unsigned char *>  TWeakPtr;
 
// заголовок страницы - всегда в памяти
struct PageHeader {
TSharedPtr  mData;  // данные - могут быть загружены или нет
long long mFilePos;   // смещение в файле страниц
 
unsigned char * LoadPage( void );  // грузит страницу с диска
TWeakPtr GetData( void );             // возвращает (слабый) указатель на данные
};
 
TWeakPtr PageHeader::GetData( void )
{
if (mData.isNull())    // страница не загружена?
 mData = TSharedPtr(LoadPage());  // загружаем ее, счетчик = 1
return mData.toWeakRef();            // теперь счетчик = 2, когда слабый указатель удалится, -1
}
 
Так правильно?  Улыбающийся
Записан
Akon
Гость
« Ответ #14 : Декабрь 28, 2010, 21:18 »

Не совсем. Для определенности - макс. размер кэша = 2, кол-во страниц в файле > 2 (т.е. больше размера кэша - общий случай).

Код:
class Cache
{
public:
    typedef QSharedPointer<const char> PageData;
    Cache(QIODevice& file) : file_(file), pages_(CacheSize) {}
   
    // Возвращает страницу данных; вызывается одновременно из нескольких потоков.
    const PageData& pageData(qint64 offset) const
    {
        QMutexLocker locker(&mutex_);

        // страница уже содержится - просто вернуть ее
        Q_FOREACH(const Page& page, pages_)
            if (page.offset == offset)
                return page.data;
           
        // страница не содержится - вытеснить самую старую, загрузить и вернуть новую
        pages_.dequeue();
        Page page(offset, PageData(loadPageFromDisk(offset), &pageDeleter));
        pages_.enqueue(page)
        return page.data;
    }

private:
    static const int CacheSize = 2;
    struct Page
    {
        qint64 offset;
        PageData data;
        Page() : offset(-1) {}
        Page(qint64 offset, const PageData& data) : offset(offset), data(data) {}     
    }
    typedef QQueue<Page> Pages;
    File& file_;
    Pages pages_;
    mutable QMutex mutex_;

    // загружает страницу с диска; например, память выделяется с помощью operator new[]
    const char* loadPageFromDisk(qint64 offset) const {...}
    static void pageDeleter(const char* data)
    {
        delete[] data;
    }
}

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

Итак, пусть pageData() вызван одновременно из 3-х потоков для разных страниц. В последнем (третьем) вызове из кэша будут удалены самые первые данные, т.е. данные, которые в настоящий момент активно юзает первый поток. Счетчик ссылок для этих данных упадет до 1, и когда первый поток их обработает, память освободится.
Записан
Страниц: [1] 2 3 ... 5   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.092 секунд. Запросов: 23.