如果某一数据需要初始化之后再进行多线程共享,或者某一资源大多数时候都是只读操作,那么此时如果使用互斥锁将会造成比较大的性能浪费。在传统的系统编程中可以使用读写锁来完成,但是C++11标准并没有将读写锁纳入标准库。虽然没有提供读写锁,但是我们可以很容易的实现一个自己的读写锁,同时C++11标准库也提供了数据初始化操作——std::once_flag和std::once_call——它们保证相关的操作只在一个线程上完成。
初始化竞争
std::once_flag 和 std::call_once
C++标准库提供了 std::once_flag 和 std::call_once 条件竞争,它一般应用于条件初始化中。比起锁住互斥量,并显式的检查指针,每个线程只需要使用std::call_once,在std::call_once的结束时,它保证相关资源已经被安全的初始化完成了。使用std::call_once比显式使用互斥量消耗的资源更少,特别是当初始化完成后。
call_once调用后为啥资源就是已经线程安全的初始化了呢??实际上就是cpp保证了std::call_once的调用和执行是线程安全的,至于你要在该函数里面做什么,那是你自己的事。如果你没有在里面初始化相关的资源,那么程序肯定是不能按照你的预期执行的。
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();
}
实际上,要实现一个我们自己的call_once也是很简单的,只需要提供一个std::atomit
static初始化竞争
还有一种情形的初始化过程中潜存着条件竞争:其中一个局部变量被声明为static类型。这种变量的在声明后就已经完成初始化;对于多线程调用的函数,这就意味着这里有条件竞争——抢着去定义这个变量。在很多C++11之前的编译器(译者:不支持C++11标准的编译器),在实践过程中,这样的条件竞争是确实存在的,因为在多线程中,每个线程都认为他们是第一个初始化这个变量的线程;或一个线程对变量进行初始化,而另外一个线程要使用这个变量时,初始化过程还没完成。在C++11标准中,这些问题都被解决了:初始化及定义完全在一个线程中发生,并且没有其他线程可在初始化完成前对其进行处理,条件竞争终止于初始化阶段,这样比在之后再去处理好的多。在只需要一个全局实例情况下,这里提供一个std::call_once的替代方案
class my_class;
my_class& get_my_class_instance()
{
static my_class instance; // 在C++11中是线程安全的初始化过程
return instance;
}
读写锁
之所以要有读写锁,是因为有些情况下被共享的资源很少被修改,这样如果还是使用mutex进行同步,那么如果只是进行读取操作的线程仍然需要排队加锁,这样浪费大量的CPU时间。
读写锁的作用:
- 拥有写锁的时候,其他线程不能读写
- 拥有读锁的时候,其他线程可以读,但是不能写
cpp11没有引入读写锁,但是boost库提供了共享锁——boost::shared_mutex。当任一线程拥有一个共享锁时,这个线程就会尝试获取一个独占锁,直到其他线程放弃他们的锁;同样的,当任一线程拥有一个独占锁时,其他线程就无法获得共享锁或独占锁,直到第一个线程放弃其拥有的锁。
#include <map>
#include <string>
#include <mutex>
#include <boost/thread/shared_mutex.hpp>
class dns_entry;
class dns_cache
{
std::map<std::string,dns_entry> entries;
mutable boost::shared_mutex entry_mutex;
public:
dns_entry find_entry(std::string const& domain) const
{
boost::shared_lock<boost::shared_mutex> lk(entry_mutex); // 1,获取共享锁,其他程序仍然可以调用find_entry并且不会阻塞,但是如果调用update_or_add_entry则会阻塞
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<boost::shared_mutex> lk(entry_mutex); // 2,获取独占锁,其他程序不管调用find_entry还是update_or_add_entry都会阻塞直到更新完毕
entries[domain]=dns_details;
}
};