线程安全容器

时间:2011-10-07 07:49:10

标签: c++ multithreading

伪代码中有一些示例性的容器类:

class Container
{
public:
  Container(){}
  ~Container(){}
  void add(data new)
  {
    // addition of data
  }
  data get(size_t which)
  {
    // returning some data
  }
  void remove(size_t which)
  {
    // delete specified object
  }

private:
  data d;
};

如何使这个容器成为线程安全的?我听说过互斥体 - 应该放置这些互斥体?互斥是静态的,还是全局的?在C ++中,这个任务有什么好的库?

5 个答案:

答案 0 :(得分:8)

首先,只要您要使用多个实例,互斥锁就不应该是静态的。在许多情况下,您应该或不应该使用它们。所以没有看到你的代码就很难说了。请记住,它们用于同步对共享数据的访问。因此,将它们放在修改或依赖对象状态的方法中是明智的。在你的情况下,我将使用一个互斥锁来保护整个对象并锁定所有三种方法。像:

class Container
{
public:
  Container(){}
  ~Container(){}
  void add(data new)
  {
    lock_guard<Mutex> lock(mutex);
    // addition of data
  }
  data get(size_t which)
  {
    lock_guard<Mutex> lock(mutex);
    // getting copy of value
    // return that value
  }
  void remove(size_t which)
  {
    lock_guard<Mutex> lock(mutex);
    // delete specified object
  }

private:
  data d;
  Mutex mutex;
};

答案 1 :(得分:4)

英特尔线程构建模块(TBB)为C ++提供了许多线程安全的容器实现。它是开源的,您可以从http://threadingbuildingblocks.org/ver.php?fid=174下载。

答案 2 :(得分:1)

添加互斥锁作为类的实例变量。在构造函数中初始化它,并在每个方法的开头锁定它,包括析构函数,并在方法结束时解锁。为所有类实例(静态成员或仅在gloabl范围中)添加全局互斥锁可能会降低性能。

答案 3 :(得分:1)

Max Khiszinsky也是一个非常好的无锁容器(包括地图)集合

LibCDS 1 并发数据结构

以下是文档页面:

http://libcds.sourceforge.net/doc/index.html

开始时可能会有点吓人,因为它完全是通用的,需要您注册一个选定的垃圾收集策略并初始化它。当然,线程库是可配置的,你需要初始化那个:)

有关入门信息,请参阅以下链接:

以下是“main”的基本模板:

#include <cds/threading/model.h>    // threading manager
#include <cds/gc/hzp/hzp.h>         // Hazard Pointer GC

int main()
{
    // Initialize \p CDS library
    cds::Initialize();

    // Initialize Garbage collector(s) that you use 
    cds::gc::hzp::GarbageCollector::Construct();

    // Attach main thread 
    // Note: it is needed if main thread can access to libcds containers
    cds::threading::Manager::attachThread();

    // Do some useful work 
    ...

    // Finish main thread - detaches internal control structures
    cds::threading::Manager::detachThread(); 

    // Terminate GCs 
    cds::gc::hzp::GarbageCollector::Destruct();

    // Terminate \p CDS library
    cds::Terminate();
}

不要忘记附加您正在使用的任何其他主题:

#include <cds/threading/model.h>

int myThreadFunc(void *)
{
   // initialize libcds thread control structures
   cds::threading::Manager::attachThread();

   // Now, you can work with GCs and libcds containers
   ....

  // Finish working thread
  cds::threading::Manager::detachThread(); 
}

1 不要与Google的紧凑型数据结构库混淆

答案 4 :(得分:1)

首先:在线程之间共享可变状态是 hard 。您应该使用已经过审核和调试的库。

现在有人说,有两个不同的功能问题:

  • 您希望容器提供安全的原子操作
  • 您希望容器提供安全的多个操作

多个操作的想法是必须在单个实体的控制下连续执行对同一容器的多次访问。它们要求调用者在事务持续期间“保留”互斥锁,以便只有 it 才能更改状态。

<强> 1。原子操作

这个似乎简单:

  • 向对象添加互斥锁
  • 在每个方法的开头抓取一个带有RAII锁的互斥锁

不幸的是,这也是完全错误的。

问题是重新入侵。有些方法可能会在同一个对象上调用其他方法。如果那些人再一次试图抓住互斥锁,你就会陷入僵局。

可以使用 re-entrant 互斥锁。它们有点慢,但允许同一个线程尽可能多地锁定给定的互斥锁。解锁的数量应与锁的数量相匹配,因此RAII。

另一种方法是使用调度方法:

class C {
public:
  void foo() { Lock lock(_mutex); foo_impl(); }]

private:
  void foo_impl() { /* do something */; foo_impl(); }
};

public方法是private工作方法的简单转发器,只需锁定即可。然后,只需确保私有方法永远不会使用互斥锁...

当然,存在从工作方法中意外调用锁定方法的风险,在这种情况下会出现死锁。继续阅读以避免这种情况;)

<强> 2。多项操作

实现此目的的唯一方法是让调用者持有互斥锁。

一般方法很简单:

  • 将一个互斥锁添加到容器
  • 提供此方法的句柄
  • 指责来访者在访问课程时永远不会忘记持有互斥锁

我个人更喜欢 saner 方法。

首先,我创建一个“数据包”,它只表示类数据(+一个互斥锁),然后我提供一个Proxy,负责获取互斥锁。数据被锁定,以便代理只能访问该状态。

class ContainerData {
protected:
  friend class ContainerProxy;
  Mutex _mutex;

  void foo();
  void bar();

private:
  // some data
};


class ContainerProxy {
public:
  ContainerProxy(ContainerData& data): _data(data), _lock(data._mutex) {}

  void foo() { data.foo(); }
  void bar() { foo(); data.bar(); }
};

请注意,Proxy调用自己的方法是完全安全的。互斥体将自动释放互斥锁。

如果需要多个代理,互斥锁仍然可以重入。但实际上,当涉及多个代理时,它通常会变成一团糟。在调试模式下,还可以添加一个“检查”,表示该线程尚未保留互斥锁(如果存在则断言)。

第3。提醒

使用锁定容易出错。死锁是导致错误的常见原因,只要您有两个互斥锁(或者一个并且重新入侵)就会发生死锁。如果可能,请选择使用更高级别的替代方案。