无锁代码中的延迟初始化

时间:2013-10-08 10:03:34

标签: c++ multithreading c++11 atomic

假设我有一个原子指针:

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等于null,则所有这些都将分配内存,其中N-1将释放agian。

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

2 个答案:

答案 0 :(得分:1)

我猜你可以使用一些众所周知的值(比如全局的地址)作为一个其他线程已在进行分配的标志,将指针作为你的互斥锁。

所以,你的值是:NULL - &gt;魔术“正在进行分配”指针 - &gt;实际分配。

代码会执行以下操作:

  • 加载地址:它将具有以下值之一:
    1. NULL:具有魔术值的CAS
      • CAS成功吗?如果是,我们正在进行分配,每个人都知道
        • 进行分配,存储新地址,我们已经完成(不应该使用CAS,因为我们已经保证将第一个CAS排除在外)
      • 不,然后其他人正在进行分配,请回到1
    2. 地址不是NULL,而是神奇的值
      • 所以有人已经在进行分配 - 只需等到它改变,然后使用最终值
    3. 既不是NULL也不是魔术,所以它已经是一个真正的分配值 - 只需使用它

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

答案 1 :(得分: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 );
}

如果分配失败,则抛出异常并且不启动任何线程。