C++并发实战18: 线程安全的查找表和链表

       经常遇见根据关键字查找内容的应用如DNS查询,标准库的std::map系列可供选择,但是它们是非线程安全的,一个线程安全的查找表实现如下,其主要是通过hash函数将各个key分散到具体的bucket中去,每个bucket带有一个共享锁boost::shared_mutex,从而实现线程安全的高并发数据结构:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

template >
class threadsafe_lookup_table//只针对每个bucket上锁,而全局的vector不上锁,从而降低了锁的粒度。若使用map则是简单的操作都需要对整个map上锁
{
private:
    class bucket_type//具体的桶bucket数据结构,带有共享锁boost::shared_mutex
    {
    private:
        typedef std::pair bucket_value;
        typedef std::list bucket_data;
        typedef typename bucket_data::iterator bucket_iterator;

        bucket_data data;
        mutable boost::shared_mutex mutex;

        bucket_iterator find_entry_for(Key const& key) const
        {
            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 lock(mutex);
            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 lock(mutex);
            bucket_iterator const found_entry=find_entry_for(key);
            if(found_entry==data.end())
            {
                data.push_back(bucket_value(key,value));//若push_back抛出异常不会影响原来的值
            }
            else
            {
                found_entry->second=value;//赋值抛出异常,原始值仍然为改变
            }
        }
    
        void remove_mapping(Key const& key)
        {
            std::unique_lock lock(mutex);
            bucket_iterator const found_entry=find_entry_for(key);
            if(found_entry!=data.end())
            {
                data.erase(found_entry);
            }
        }
    };
    
    std::vector > buckets;//vector里有多个桶bucket,通过hash函数将key散列到一个具体的bucket中去
    Hash hasher;

    bucket_type& get_bucket(Key const& key) const//从vector找出key散列后对应的bucket
    {
        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
 

    

    这对链表实现一个线程安全的版本,链表中每个元素都持有一个mutex,从而对链表的每一个操作至多持有当前节点和下一节点的mutex,这样锁的粒度更细了提高了并发的性能:

#include 
#include 

template
class threadsafe_list
{
    struct node//每个节点持有一个mutex
    {
        std::mutex m;
        std::shared_ptr data;
        std::unique_ptr next;

        node():
            next()
        {}
        
        node(T const& value):
            data(std::make_shared(value))
        {}
    };
    
    node head;

public:
    threadsafe_list()
    {}

    ~threadsafe_list()
    {
        remove_if([](T const&){return true;});
    }

    threadsafe_list(threadsafe_list const& other)=delete;
    threadsafe_list& operator=(threadsafe_list const& other)=delete;
    
    void push_front(T const& value)//从头部插入一个节点只需要锁住head
    {
        std::unique_ptr new_node(new node(value));//在临界区外new,这样既可以减小临界区又可以避免临界区中抛出异常
        std::lock_guard lk(head.m);
        new_node->next=std::move(head.next);//unique_ptr不能直接赋值,但可以通过reset或move
        head.next=std::move(new_node);
    }

    template
    void for_each(Function f)//针对链表中每个元素执行f
    {
        node* current=&head;
        std::unique_lock lk(head.m);
        while(node* const next=current->next.get())
        {
            std::unique_lock next_lk(next->m);//锁住当前节点后,立即释放上一个节点
            lk.unlock();//
            f(*next->data);
            current=next;
            lk=std::move(next_lk);//向后移动,unique_lock is moveable not copyable,而lock_guard不具备移动语义,可见unique_lock比lock_guard灵活
        }
    }

    template
    std::shared_ptr find_first_if(Predicate p)//找到链表中事谓词P返回true的第一个元素
    {
        node* current=&head;
        std::unique_lock lk(head.m);
        while(node* const next=current->next.get())
        {
            std::unique_lock next_lk(next->m);
            lk.unlock();//拿到当前元素的锁后立即释放上一个锁
            if(p(*next->data))//谓词P返回true,那么返回该元素
            {
                return next->data;
            }
            current=next;
            lk=std::move(next_lk);
        }
        return std::shared_ptr();
    }

    template
    void remove_if(Predicate p)//删除哪些使得谓词P返回true的元素
    {
        node* current=&head;
        std::unique_lock lk(head.m);
        while(node* const next=current->next.get())
        {
            std::unique_lock next_lk(next->m);
            if(p(*next->data))
            {
                std::unique_ptr old_next=std::move(current->next);
                current->next=std::move(next->next);//重置连接
                next_lk.unlock();//注意这里并没有对lk解锁或者重置
            }
            else
            {
                lk.unlock();
                current=next;
                lk=std::move(next_lk);
            }
        }
    }
};



你可能感兴趣的:(C++并发实战,C++并发实战(C++11),C++并发实战17,线程安全的查找表和链)