как обрабатывать кешированные структуры данных с многопоточностью (например, openmp)

Я использую OpenMP для распараллеливания нашей библиотеки C ++. Там у нас есть разные места, где мы избегаем пересчета некоторых вещей, сохраняя результаты в переменной (т.е. кешируя результат для повторного использования). Однако такое поведение скрыто от пользователя в методах класса. Например, при первом использовании метода кеш будет заполнен. Все последующие использования будут просто читать из кеша.

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

Пример класса может выглядеть следующим образом

class A {
public:
   A() : initialized(false)
     {}
   int get(int a)
      { 
#pragma omp critical(CACHING)
        if (!initialized)
          initialize_cache();
        return cache[a];
      }
private:
   bool initialized;
   void initialize_cache()
     {
       // do some heavy stuff
       initialized=true;
     }
   int *cache;
};

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

Есть предложения по улучшению этого? В идеале решение было бы совместимо со старыми версиями OpenMP (даже v2 для Visual Studio ...)

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


person krthie    schedule 16.01.2015    source источник
comment
Можете ли вы создать новый класс и глобальную переменную этого класса и заставить новый ctor выполнить ваш init?   -  person brian beuning    schedule 16.01.2015
comment
в реальной жизни мой объект set_up во время выполнения с разными параметрами. когда это set_up, кеш будет очищен, так как он больше не подходит. Итак, нет, у меня не может быть глобальной переменной. Кроме того, поскольку время вычисления содержимого кеша довольно велико и оно требуется не во всех случаях использования, в настоящее время мы избегаем инициализации кеша, если он нам действительно не нужен. (вы не могли знать этого из вопроса. Я предполагаю, что это происходит, когда вы пытаетесь сделать простой пример).   -  person krthie    schedule 16.01.2015


Ответы (2)


Вы можете использовать шаблон «Double-Checked-Locking (DCL)» с атомарной операцией OpenMP, Требуется OpenMP v3.1 или более поздняя версия (_1 _ / _ 2_ параметр omp atomic pragma).

class A {
public:
   A() : initialized(false)
     {}
   int get(int a)
      {
        bool b;
#pragma omp atomic read
        b = initialized;
        if (!b) {
#pragma omp critical(CACHING)
          // you must recheck in critical section
          if (!initialized)
            initialize_cache();
        }
        return cache[a];
      }
private:
   bool initialized;
   void initialize_cache()
     {
       // do some heavy stuff
#pragma omp atomic write
       initialized = true;
     }
   int *cache;
};

... Но я рекомендую один из следующих вариантов, а не шаблон DCL:

  • pthread_once() (библиотека потоков POSIX)
  • std::call_once() (Стандартная библиотека C ++ 11)
  • потокобезопасная static переменная (функция основного языка C ++ 11)
person yohjp    schedule 17.01.2015
comment
есть ли шанс сделать это с более ранними версиями openmp? Мне нужно быть максимально кроссплатформенным. Решает ли это решение проблемы с шаблоном DCL (см., Например, michaelsuess.net/publications/suess_leopold_singleton_07.pdf) - person krthie; 21.01.2015
comment
AFAIK, до OpenMP v3.0 нет переносимого способа, кроме простого использования конструкции omp critical. И я думаю, что этот код хорошо работает в модели памяти OpenMP, потому что доступ initialized обозначен как атомарный и в любом случае защищен критической секцией. - person yohjp; 21.01.2015

Эффективный синглтон - лучший выбор для вас. Пожалуйста, проверьте здесь. эффективный потокобезопасный синглтон в C ++

Кроме того, Херб Саттер рассказывает об этом на CppCon 2014

Вот полный фрагмент кода из видео, которое я показал выше:

class Foo {
public:
    static Foo* Instance();
private:
    Foo() {init();}
    void init() { cout << "init done." << endl;} // your init cache function.
    static atomic<Foo*> pinstance;
    static mutex m_;
};

atomic<Foo*> Foo::pinstance { nullptr };
std::mutex Foo::m_;

Foo* Foo::Instance() {
  if(pinstance == nullptr) {
    lock_guard<mutex> lock(m_);
    if(pinstance == nullptr) {
        pinstance = new Foo();
    }
  }
  return pinstance;
}

запустите код здесь: http://ideone.com/olvK13

person qqibrow    schedule 16.01.2015
comment
благодаря. Мой кеш должен быть объектно-ориентированным, т.е. не одноэлементным, но я признаю, что это было неясно из моего вопроса. - person krthie; 22.01.2015