使用TBB进行线程安全的懒惰创建?

时间:2012-02-18 22:26:02

标签: c++ multithreading thread-safety tbb

在我的C ++代码中,我保留了一个指向对象的指针 应该懒惰地创建,即仅在请求时创建。 我有以下代码,这显然不是线程安全的。

LAZY* get_lazy()
{
    if (0 == _lazy)
        _lazy = create_lazy();
    return _lazy;
}

我想知道我应该在这里使用什么样的同步? 我知道 Boost.thread 提供了对一次性初始化的支持。 但我希望只有使用 TBB + C ++的简单解决方案。 我还应该注意到......

  • 我不能将_lazy创建为静态对象(我实际上想要保留这些懒惰创建对象的无界数组)
  • 此类LAZY个对象不能过度分配(创建非常昂贵)

3 个答案:

答案 0 :(得分:2)

你需要一个本地互斥(tbb :: mutex),以确保你只创建一个懒惰对象。

#include <tbb/mutex.h>

tbb::atomic<LAZY*> _lazy;
tbb::mutex myMutex;

LAZY* GetLazy()
{
  if (0 == _lazy)
  {
    myMutex.lock();
    if (0 == _lazy)
        _lazy = create_lazy();
    myMutex.unlock();
  }
  return _lazy;
}

答案 1 :(得分:2)

偶尔可以多次拨打create_lazy是否可以?如果是这样,这是一个非常轻量级,高效的解决方案,仅使用TBB:

tbb::atomic<LAZY*> lazy;

if(!lazy)
{
    LAZY *newlazy = create_lazy();

    if(lazy.compare_and_swap(newlazy, 0))
    {
        // lazy was initialized elsewhere.
        delete newlazy;
    }
}

// use lazy.

这将比Maciej的解决方案少得多(零!)开销,但是如果在特定变量的线程之间存在争用的情况下偶尔可以多次调用create_lazy,那么它将再次起作用。

避免互斥锁和多次调用create_lazy的一种方法是使用自旋循环。如果存在争用,这将使用比互斥锁更多的CPU,但仍然是低开销:

tbb::atomic<LAZY*> lazy;
static int sentry;

if(!lazy && !lazy.compare_exchange((LAZY*)&sentry, 0))
{
    // lazy is set to a sentry value while being allocated.
    try{ lazy = create_lazy(); }
    catch(...) { lazy = 0; throw; }
}
else
{
    // yield the thread while lazy is still set to the sentry.
    while(lazy == (LAZY*)&sentry)
    {
        tbb::this_tbb_thread::yield();
    }
}

// use lazy.

答案 2 :(得分:2)

您还可以查看TBB内部如何解决此问题。要在代码中搜索的名称是atomic_do_once;它是用于延迟初始化的内部(在写入时)TBB函数。这个函数和辅助函数的定义在src / tbb_misc.h中,在其他文件中有一些地方使用它。

基本思想与@ CoryNelson的答案相同,但在三态旗帜的帮助下推广(见enum do_once_state)。需要创建一个类型为tbb::atomic<do_once_state>的静态变量,并将其与一个应该运行一次的函数/仿函数一起传递给atomic_do_once的调用。例如:

void initialize_once();
static tbb::atomic<tbb::internal::do_once_state> init_state;
/*...*/
// Safe to execute concurrently
tbb::internal::atomic_do_once( &initialize_once, init_state );

对于长时间运行的初始化,首选使用@MaciejDopieralski推荐的tbb::mutex,因为它可以通过将等待的线程置于休眠状态来避免过多的CPU使用。请注意,TBB中的大多数其他互斥口味也会旋转,而不是睡眠。