2013-10-08 233 views
2

假设我有一个原子指针:延迟初始化

std::atomic<void*> mItems; 

,并在功能,当一个线程需要访问,它首先检查它,如果它为null,线程将分配内存它:

void* lItems = std::atomic_load_explicit(&mItems, memory_order_relaxed); 
if(lItems == nullptr) 
{ 
    void* lAllocation = malloc(...); 

    if(!std::atomic_compare_exchange_strong_explicit(
     &mItems, 
     &lItems, 
     lAllocation, 
     memory_order_relaxed, 
     memory_order_relaxed)) 
    { 
     free(lAllocation); 
    } 
} 
    ... 

但如果N线程运行此方法并发看到mItems等于空,然后所有的人都将分配内存和N - 1它们将释放agian。

我如何用更好的方法编写类似的方法。

+1

Punch [“double checked locking”](http://en.wikipedia。org/wiki/Double-checked_locking)到您最喜欢的搜索引擎中。 –

+4

惊喜!这是一个解决的问题http://en.cppreference.com/w/cpp/thread/call_once –

+1

@ R.MartinhoFernandes:但我怀疑'call_once'是无锁的。 –

回答

1

我想你可以使指针成为你的互斥量,使用一些众所周知的值(比如全局地址)作为其他线程已经在分配的标志。因此,你的值是:NULL - >魔术“分配进行中”指针 - >实际分配。

的代码会做这样的事情:

  • 加载地址:它会具有以下值之一:
    1. NULL:CAS魔法值
      • 没有CAS成功吗?如果是的话,我们正在做分配,每个人都知道它
        • 做的分配,存储新地址,我们完成(不应该有CAS它,因为我们已经保证排除与第一个CAS)
      • 没有,那么别人在做分配,回到1个
    2. 地址不为空,但魔法值
      • 所以有人已经在做分配 - 只是等待,直到它的变化,并使用最终值
    3. 既不是NULL也不神奇,所以它已经是一个真正的分配值 - 只是用它

这样,只有一个线程执行的分配,但你的其他N-1个线程可能是忙等待。这是否真的更好会有所不同...

+0

谢谢,这种方法也在我脑海里。但是这与once_flag类似。我只搜索更好的方法,尽管所有这些都是可以接受的,但是为你的答案+1。 – MRB

+0

区别在于你没有单独的标志:你做一个单一的负载,只有在角落的情况下,CAS是一个单词。在通常的快速情况下,您不会发生缓存或其他开销。 – Useless

+0

我从无锁码 – MRB

0

当我得到它,只要第一个线程执行你的函数就需要这个结构,那么在启动任何线程之前如何移动分配?我的意思是,重新排列代码,以便函数以原子指针作为参数被调用,并且在调用函数的任何线程都被生成之前分配结构(如果分配失败,也可以避免创建任何线程)

类似:

std::atomic<void*> mItems; 
void func_that_uses_mItems(); 

int main() 
{ 
    mItems = init_struct(); 
    std::thread t1 { &func_that_uses_mItems }; 
    std::thread t2 { &func_that_uses_mItems }; 


    // ... join with or detach threads ... 

    return(0); 
} 

如果分配失败抛出一个异常,并没有线程启动。

+0

的角度讲,问题是我不想这样做。假设我有一个项目的数组,每个指向一个其他项目的数组,我只想初始化它们,只有当一些身体访问它们。 – MRB