多线程编程之读写锁
读写问题是一个经典的同步问题,主要针对保护很少更新的数据结构
这种同步情况。
具体的例子:
假设有一个用于存储DNS条目缓存的表,它用来将域名解析为相应的IP地址。通常,一个给定的DNS条目将在很长一段时间里保持不变——在许多情况下,DNS条目会保持数年不变。虽然随着用户访问不同的网站,新的条目可能会被不时地添加到表中,但这一数据却将在其真个生命中保持不变。定期检查缓存条目的有效性是很重要的,但是只是在细节已有实际改变的时候才会需要更新。
虽然更新是罕见的,但他们仍然会发生,并且如果这个缓存可以从多个线程访问,它就需要在更新过程中进行适当的保护,以确保所有线程在读取缓存时都不会看到损坏的数据结构。
如何高效的解决这种同步问题?
我们知道使用std::mutex
来保护数据结构就会显得特别悲观,因为这会在数据结构没有进行修改时消除并发读取数据结构的可能,因此我们需要另一种互斥元——读写锁。读写锁考虑到了两个不同的用法:由单个写
线程独占访问或共享,由多个读
线程并发访问。
新的C++11标准库并没有直接提供这样的互斥元,尽管已向标准委员会提议。由于这个建议未被接纳,例子使用由boost
库提供的实现——boost::shared_mutex
。对于更新操作,std::lock_guard<boost::shared_mutex>
和std::unique_lock<boost::shared_mutex>
可用于锁定,以取代相应的std::mutex
特化。这样确保了独占访问,就像std::mutex
那样。那些不需要更新数据结构的线程能够转而使用boost::shared_lock<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> lock(entry_mutex); //使用boost库中的boost::shared_lock进行读锁定
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> lock(entry_mutex); //使用C++11库中的std::lock_guard进行写锁定
entries[domain] = dns_details;
}
};
find_entry()
使用一个boost::shared_lock()
实例来保护它,以供共享、只读的访问;多个线程因而可以毫无问题的同时调用find_entry()
。另一方面,update_or_add_entry()
使用一个std::lock_guard<>
实例,在表被更新时提供独占访问;不仅在调用update_or_add_entry()
中其他线程被阻止进行更新,调用find_entry()
的线程也会被阻塞。