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
问题:如果你的数据更新频率低,但是读取频率高.你该怎么办?
- C++17有了解决方案,就是shared_mutex and shared_timed_mutex.
- C++14只有shared_timed_mutex.
- 而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可以递归枷锁解锁,在共有函数需要调用成员函数的时候可能会用。最好别用这个功能。如果你非要用请三思是否真的需要这样的设计.