如何使用多线程处理缓存的数据结构(例如 openmp)

Posted

技术标签:

【中文标题】如何使用多线程处理缓存的数据结构(例如 openmp)【英文标题】:how to handle cached data structures with multi-threading (e.g. openmp) 【发布时间】:2015-01-16 00:58:34 【问题描述】:

我正在使用 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 版本兼容(甚至 Visual Studio 的 v2...)

PS:这可能以前被问过,但是搜索 openmp 和缓存会在处理器缓存上抛出很多东西,这不是我想知道的......

【问题讨论】:

你能创建一个新类和该类的全局变量,并让新的 ctor 进行初始化吗? 在现实生活中,我的对象在运行时使用不同的参数设置。当它被设置时,缓存将被清除,因为它不再合适。所以,不,我不能有一个全局变量。此外,由于缓存内容的计算时间相当长,并且并非在所有使用情况下都需要它,因此我们目前避免初始化缓存,除非我们真的需要它。 (您无法从问题中知道这一点。我想在尝试做一个简单的例子时会发生这种情况)。 【参考方案1】:

您可以将 "Double-Checked-Locking(DCL) pattern" 与 OpenMP 原子操作一起使用,需要 OpenMP v3.1 或更高版本(omp atomic pragma 的read/write 选项)。

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 核心语言特性)

【讨论】:

有没有机会在早期版本的 openmp 中做到这一点?我需要尽可能跨平台兼容。此解决方案是否克服了 DCL 模式的问题(参见例如michaelsuess.net/publications/suess_leopold_singleton_07.pdf) AFAIK,在 OpenMP v3.0 之前没有可移植的方式,除了直接使用 omp critical 构造。而且我认为这段代码在 OpenMP 内存模型下运行良好,因为initialized 的访问被指定为原子访问并且无论如何都受临界区保护。【参考方案2】:

高效的单例是您的最佳选择。 请在此处查看。efficient thread-safe singleton in C++

另外,Herb Sutter talks about that in CppCon 2014

这是我上面展示的视频中的完整代码 sn-p:

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

【讨论】:

谢谢。我的缓存需要是特定于对象的,即不是单例,但我承认我的问题可能并不清楚。

以上是关于如何使用多线程处理缓存的数据结构(例如 openmp)的主要内容,如果未能解决你的问题,请参考以下文章

多线程-Java内存模型与线程

java多线程高并发的学习

JAVA多线程与并发学习总结

java多线程高并发知识总结

memcached 的多线程是什么?如何使用它们?

memcached 的多线程是什么?如何使用它们?