第六章 基于锁的并发数据结构设计

6.1 为并发设计的意义何在?

  • 设计并发数据结构,意味着多个线程可以并发的访问这个数据结构,线程可对这个数据结构做相同或不同的操作,并且每一个线程都能在自己的自治域中看到该数据结构
  • 在互斥量的保护下,同一时间内只有一个线程可以获取互斥锁。互斥量为了保护数据,显式的阻止了线程对数据结构的并发访问
  • 序列化(serialzation):线程轮流访问被保护的数据。这其实是对数据进行串行的访问,而非并发
  • 你需要对数据结构的设计进行仔细斟酌,确保其能真正并发访问
  • 减少保护区域,减少序列化操作,就能提升并发访问的潜力

6.1.1 数据结构并发设计的指导与建议(指南)

  • 有两方面需要考量:一是确保访问是安全的,二是能真正的并发访问
  • 安全访问:
  1. 确保无线程能够看到,数据结构的“不变量”破坏时的状态。

  2. 小心那些会引起条件竞争的接口,提供完整操作的函数,而非操作步骤。

  3. 注意数据结构的行为是否会产生异常,从而确保“不变量”的状态稳定。

  4. 将死锁的概率降到最低。使用数据结构时,需要限制锁的范围,且避免嵌套锁的存在。

不能在构造函数完成前,或析构函数完成后对数据结构进行访问

6.2 基于锁设计更加复杂的数据结构

编写一个使用锁的线程安全查询表

  • 查询表或字典是一种类型的值(键值)和另一种类型的值进行关联(映射的方式)
  • 查询表的使用与栈和队列不同。栈和队列上,几乎每个操作都会对数据结构进行修改,不是添加一个元素,就是删除一个,而对于查询表来说,几乎不需要什么修改
  • 并发访问时,std::map<>接口最大的问题在于——迭代器
  • 要想正确的处理迭代器,你可能会碰到下面这个问题:
    当迭代器引用的元素被其他线程删除时,迭代器在这里就是个问题了;
  • 查询表的基本操作有:

        ~~~~~~~         1) 添加一对“键值-数据”

        ~~~~~~~         2) 修改指定键值所对应的数据

        ~~~~~~~         3) 删除一组值

        ~~~~~~~         4) 通过给定键值,获取对应数据

        ~~~~~~~         5) 查询是否为空

  • 之前的线程安全指导意见,例如:不要返回一个引用,并且用一个简单的互斥锁对每一个成员函数进行上锁,以确保每一个函数线程安全
  • 最有可能的条件竞争在于,当一对“键值-数据”加入时;当两个线程都添加一个数据,那么肯定一个先一个后。一种方式是合并**“添加”和“修改”**操作,为一个成员函数(用于避免一个线程添加的新key,是下一个线程要添加的新key)
  • 接口角度看,有一个问题很是有趣,那就是任意(if any)部分获取相关数据:
  • 一种选择是允许用户提供一个“默认”值,在键值没有对应值的时候进行返回
mapped_type get_value(key_type const& key, mapped_type default_value);
  • 可以扩展成返回一个std::pair<mapped_type, bool>来代替mapped_type实例,其中bool代表返回值是否是当前键对应的值
  • 另一个选择是,返回一个有指向数据的智能指针;当指针的值是NULL时,那么这个键值就没有对应的数据

  • 但是在同一时间内,也只有一个线程能对数据结构进行修改

为细粒度锁设计一个映射结构

  • 为了允许细粒度锁能正常工作,需要对于数据结构的细节进行仔细的考虑,而非直接使用已存在的容器,例如std::map<>
  • 这里列出三个常见关联容器的方式:
  1. 二叉树,比如:红黑树

  2. 有序数组

  3. 哈希表

  • 二叉树的方式,不会对提高并发访问的概率;每一个查找或者修改操作都需要访问根节点,因此,根节点需要上锁。虽然,访问线程在向下移动时,这个锁可以进行释放,但相比横跨整个数据结构的单锁,并没有什么优势。
  • 有序数组是最坏的选择,因为你无法提前言明数组中哪段是有序的,所以你需要用一个锁将整个数组锁起来。
  • 哈希表: 假设有固定数量的桶,每个桶都有一个键值(关键特性),以及散列函数。这就意味着你可以安全的对每个桶上锁;当你再次使用互斥量(支持多读者单作者)时,你就能将并发访问的可能性增加N倍,这里N是桶的数量
    缺点:对于键值的操作,需要有合适的函数
线程安全的查询表代码实现
template<typename Key,typename Value,typename Hash=std::hash<Key> >
class threadsafe_lookup_table
{
private:
  class bucket_type
  {
  private:
    typedef std::pair<Key,Value> bucket_value;
    typedef std::list<bucket_value> bucket_data;
    typedef typename bucket_data::iterator bucket_iterator;

    bucket_data data;
    mutable boost::shared_mutex mutex;  // 1

    bucket_iterator find_entry_for(Key const& key) const  // 2
    {
      return std::find_if(data.begin(),data.end(),
      [&](bucket_value const& item)
      {return item.first==key;});
    }
  public:
    Value value_for(Key const& key,Value const& default_value) const
    {
      boost::shared_lock<boost::shared_mutex> lock(mutex);  // 3
      bucket_iterator const found_entry=find_entry_for(key);
      return (found_entry==data.end())?
        default_value:found_entry->second;
    }

    void add_or_update_mapping(Key const& key,Value const& value)
    {
      std::unique_lock<boost::shared_mutex> lock(mutex);  // 4
      bucket_iterator const found_entry=find_entry_for(key);
      if(found_entry==data.end())
      {
        data.push_back(bucket_value(key,value));
      }
      else
      {
        found_entry->second=value;
      }
    }

    void remove_mapping(Key const& key)
    {
      std::unique_lock<boost::shared_mutex> lock(mutex);  // 5
      bucket_iterator const found_entry=find_entry_for(key);
      if(found_entry!=data.end())
      {
        data.erase(found_entry);
      }
    }
  };

  std::vector<std::unique_ptr<bucket_type> > buckets;  // 6
  Hash hasher;

  bucket_type& get_bucket(Key const& key) const  // 7
  {
    std::size_t const bucket_index=hasher(key)%buckets.size();
    return *buckets[bucket_index];
  }

public:
  typedef Key key_type;
  typedef Value mapped_type;

  typedef Hash hash_type;
  threadsafe_lookup_table(
    unsigned num_buckets=19,Hash const& hasher_=Hash()):
    buckets(num_buckets),hasher(hasher_)
  {
    for(unsigned i=0;i<num_buckets;++i)
    {
      buckets[i].reset(new bucket_type);
    }
  }

  threadsafe_lookup_table(threadsafe_lookup_table const& other)=delete;
  threadsafe_lookup_table& operator=(
    threadsafe_lookup_table const& other)=delete;

  Value value_for(Key const& key,
                  Value const& default_value=Value()) const
  {
    return get_bucket(key).value_for(key,default_value);  // 8
  }

  void add_or_update_mapping(Key const& key,Value const& value)
  {
    get_bucket(key).add_or_update_mapping(key,value);  // 9
  }

  void remove_mapping(Key const& key)
  {
    get_bucket(key).remove_mapping(key);  // 10
  }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值