C++并发编程(CH03)[其它保护共享数据的手段-03]

Alternative facilities for protecting shared data

Protecting shared data during initialization

std::shared_ptr<some_resource> resource_ptr;
void foo()
{
  if(!resource_ptr)
    {
      resource_ptr.reset(new some_resource); //假如这一步很费资源.
    }
  resource_ptr->do_something(); // 假如这一步不需要锁就可以并发执行.
}

问题:怎么设计一个高效的访问策略.

std::shared_ptr<some_resource> resource_ptr;
std::mutex resource_mutex;
void foo()
{
  std::unique_lock<std::mutex> lk(resource_mutex);
  if(!resource_ptr)
    {
      resource_ptr.reset(new some_resource);
    }
  lk.unlock();
  resource_ptr->do_something();

}

上面的策略,并不好.每次进入foo都需要获取锁并执行检查.

void undefined_behaviour_with_double_checked_locking()
{
  if(!resource_ptr)
    {
      std::lock_guard<std::mutex> lk(resource_mutex);
      if(!resource_ptr)                          //<--2
        {
          resource_ptr.reset(new some_resource); //<--3
        }
        // 臭名昭著的双重检查.
        //(这是有问题的,会导致资源竞争,因为指针如果有了,并不代表数据初始化好了。这样导致访问数据不一致)
    }
  resource_ptr->do_something();
}

特别要注意双重检查非常不好,因为指针出现了,并不意味数据也初始化完成.
怎么解决?C++提供了机制.

std::shared_ptr<some_resource> resource_ptr;
std::once_flag resource_flag;                  //<--1
void init_resource()
{
  resource_ptr.reset(new some_resource);
}
void foo()
{
  std::call_once(resource_flag,init_resource);
  //(一种安全的共享资源初始化)
  resource_ptr->do_something();
}

注意once_flag也是不能拷贝也不能移动的对象

class X
{
private:
  connection_info connection_details;
  connection_handle connection;
  std::once_flag connection_init_flag;
  void open_connection()
  {
    connection=connection_manager.open(connection_details);
  }
public:
  X(connection_info const& connection_details_):
    connection_details(connection_details_)
  {}
  void send_data(data_packet const& data)                       
  {
    std::call_once(connection_init_flag,&X::open_connection,this); 
    connection.send_data(data);
  }
  data_packet receive_data()                                       
  {
    std::call_once(connection_init_flag,&X::open_connection,this); // 注意传递类中成员函数方法的形式.
    return connection.receive_data();
  }
};

static : 还有一个潜在的危险地方. local
static.也就是函数内的静态变量的初始化可能有问题.

class my_class;
my_class& get_my_class_instance()
{
  static my_class instance;
  //(sai:静态变量的初始化在C++11前是不安全的,但是C++11及之后的编译器是安全的。)
  return instance;
}

Protecting rarely updated data structures

问题:如果你的数据更新频率低,但是读取频率高.你该怎么办?

  1. C++17有了解决方案,就是shared_mutex and shared_timed_mutex.
  2. C++14只有shared_timed_mutex.
  3. 而C++11什么也没有.所以你想在C++11中使用这种mutex.你需要借助boost库.
#include <map>
#include <string>
#include <mutex>
#include <shared_mutex>
class dns_entry;
class dns_cache
{
    std::map<std::string,dns_entry> entries;
    mutable std::shared_mutex entry_mutex;
public:
    dns_entry find_entry(std::string const& domain) const
    {
        std::shared_lock<std::shared_mutex> lk(entry_mutex); // 可共享的进入共享锁定模式.
        std::map<std::string,dns_entry>::const_iterator const it=
            entries.find(domain);
        return (it==entries.end())?dns_entry():it->second;
    }
    void update_or_add_entry(std::string const& domain,
                             dns_entry const& dns_details)
    {
        std::lock_guard<std::shared_mutex> lk(entry_mutex); // 等待所有的共享锁定线程结束之后才可以进入锁定区.
        entries[domain]=dns_details; 
    }
};
//如何使用shared_mutex)

当然你需要面对饥饿问题.如果很多读操作,让共享锁一直无法空闲下来.那么写锁就无法获取得到.

Recursive locking

recursive_mutex可以递归枷锁解锁,在共有函数需要调用成员函数的时候可能会用。最好别用这个功能。如果你非要用请三思是否真的需要这样的设计.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值