C++并发编程----利用锁实现线程安全的数据结构(《C++ Concurrency in Action》 读书笔记)

Posted 小丑快学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++并发编程----利用锁实现线程安全的数据结构(《C++ Concurrency in Action》 读书笔记)相关的知识,希望对你有一定的参考价值。

本文为《C++ Concurrency in Action》 读书笔记,对其中的一些知识点进行总结。阅读这本书前建议先阅读《Effective C++》

线程安全队列

template<typename T>
class threadsafe_queue
{
private:
	//一个链表节点,用链表最为队列的数据结构
	struct node
	{
		std::shared_ptr<T> data;
		std::unique_ptr<node> next;
	};
	
	//头节点互斥量
	std::mutex head_mutex;
	//头节点指针
	std::unique_ptr<node> head;
	
	//尾节点互斥量
	std::mutex tail_mutex;
	//尾节点指针,尾节点指针不能是unique_ptr,
	//因为,对于尾节点会同时有两个指针指向它,他的前驱和tail指针,不能unique_ptr的的目的便是防止共享,
	//所以,只能用一般的指针来指示尾节点
	node* tail;
	
	//条件变量,用于等待和唤醒操作
	std::condition_variable data_cond;
	
	/*
	*	因为使用私有数据结构node,因此这些函数需要类内部实现
	*/
	
	//互斥得获取尾节点
	node* get_tail() {
		std::lock_guard<std::mutex> tail_lock(tail_mutex); 
		return tail;
	}
	//弹出头节点,只需要直接弹出,互斥以及判空有调用的函数保证
	//抽象该函数可以避免代码重复
	std::unique_ptr<node> pop_head() {
		std::unique_ptr<node> old_head = std::move(head);
		head = std::move(old_head->next);
		return old_head;
	}
	//当有数据到达时唤醒一个线程,并让该线程持有头节点互斥量
	std::unique_lock<std::mutex> wait_for_data() {
		std::unique_lock<std::mutex> head_lock(head_mutex);
		data_cond.wait(head_lock, [&] {return
			           head.get() != get_tail(); });
		return std::move(head_lock);
	}
	//当有数据到达后,一个线程被唤醒,并持有头节点锁,同时将头节点弹出后释放锁
	std::unique_ptr<node> wait_pop_head() {
		std::unique_lock<std::mutex> head_lock(wait_for_data()); 
		return pop_head();
	}
	//上述函数的重载
	std::unique_ptr<node> wait_pop_head(T& value){
		std::unique_lock<std::mutex> head_lock(wait_for_data()); 
			value = std::move(*head->data);
		return pop_head();
	}

	//如果为空则返回空节点,否则返回头节点
	std::unique_ptr<node> try_pop_head()
	{
		std::lock_guard<std::mutex> head_lock(head_mutex);
		if (head.get() == get_tail())//判空
		{
			return std::unique_ptr<node>();
		}
		return pop_head();
	}
	//上述函数的重载
	std::unique_ptr<node> try_pop_head(T& value)
	{
		std::lock_guard<std::mutex> head_lock(head_mutex);
		if (head.get() == get_tail())
		{
			return std::unique_ptr<node>();
		}
		value = std::move(*head->data);
		return pop_head();
	}

public:
	//该队列中将会有一个节点不存放数据,用于避免条件竞争,
	//避免当队列中只有一个数据时,头指针和尾指针之间发生条件竞争
	threadsafe_queue() :
		head(new node), tail(head.get())
	{}
	threadsafe_queue(const threadsafe_queue& other) = delete;
	threadsafe_queue& operator=(const threadsafe_queue&
		other) = delete;
	//push于类外实现
	void push(T new_value);
	
	//try_pop将不会阻塞等待数据的到达
	std::shared_ptr<T> try_pop()
	{
		std::unique_ptr<node> old_head = try_pop_head();
		return old_head ? old_head->data : std::shared_ptr<T>();
	}
	bool try_pop(T& value)
	{
		std::unique_ptr<node> const old_head = try_pop_head(value);
		return old_head;//将会发生隐式类型转换
	}

	bool empty()
	{
		std::lock_guard<std::mutex> head_lock(head_mutex);
		return (head.get() == get_tail());
	}

	//如果队为空,则线程将会阻塞等待,直到被唤醒
	std::shared_ptr<T> wait_and_pop(){
		std::unique_ptr<node> const old_head = wait_pop_head();
		return old_head->data;
	}

	void wait_and_pop(T& value){
		std::unique_ptr<node> const old_head = wait_pop_head(value);
	}

};

//push,数据入队列
template<typename T>
void threadsafe_queue<T>::push(T new_value)
{
	std::shared_ptr<T> new_data(
		std::make_shared<T>(std::move(new_value)));
	std::unique_ptr<node> p(new node);
	{
		std::lock_guard<std::mutex> tail_lock(tail_mutex);
		tail->data = new_data;
		node* const new_tail = p.get();
		tail->next = std::move(p);
		tail = new_tail;
	}
	data_cond.notify_one();
}

用简单的消费者和生产者测试队列:

void fun1(threadsafe_queue<int> & que) {
	for (int i = 0; i < 15; i++) {
		que.push(i);
		this_thread::sleep_for(std::chrono::milliseconds(1000));
	}
}

void fun2(threadsafe_queue<int>& que) {
	int i = 0;
	while (i < 9) {
		que.wait_and_pop(i);
		cout <<"comsymer_1 get number:" <<i << endl;
	}
}
void fun3(threadsafe_queue<int>& que) {
	int i = 0;
	while (i < 9) {
		que.wait_and_pop(i);
		cout << "comsymer_2 get number:" << i << endl;
	}
}

int main() {

	threadsafe_queue<int> que;
	
	thread producer(fun1,ref(que));
	thread comsumer_1(fun2,ref(que));
	thread comsumer_2(fun3, ref(que));
	producer.join();
	comsumer_1.join();
	comsumer_2.join();
	return 0;
}

支持并发的Map

采用c++标程序库中的hash< typename T> 来设置哈希函数,每个桶由链表构成,每个Map中内嵌一个bucket的类。

template<typename Key, typename Value, typename Hash = std::hash<Key> >
class threadsafe_lookup_table
{
private:
    //一个buket中存放相同的键(通过hansh函数计算后的函数值),
    //对应的值(一个pair)存放于一个链表中。
    class bucket_type
    {
    private:
        typedef std::pair<Key, Value> bucket_value;
        typedef std::list<bucket_value> bucket_data;
        typedef typename bucket_data::iterator bucket_iterator;
        mutable bucket_data data;// data 加上关键字mutable,因为需要在const修饰的函数中修改桶的类
        mutable boost::shared_mutex mutex; // 允许多个线程同时读,但某个时刻仅一个线程可以修改
        
        /*
         *一个给定的key,返回该key对应的数据的迭代器
         */
        bucket_iterator find_entry_for(Key const& key) const  
        {
           auto it = std::find_if(data.begin(), data.end(),
               [&](bucket_value const& item)
               {return item.first == key; });
           return it;
        }
    public:

        /*
        * 给定一个key,返回其对应的值
        */
        Value value_for(Key const& key, Value const& default_value)
            const
        {
            boost::shared_lock<boost::shared_mutex> 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<boost::shared_mutex> lock(mutex); //只能单个线程能够修改数据
            bucket_iterator const found_entry = find_entry_for(key);
            if (found_entry == data.end())
            {
                data.push_back(bucket_value(key, value));
            }
            else
            {
                found_entry->second = value;
            }
        }

        /*
        * 删除数据
        */
        void remove_mapping(Key const& key)
        {
            std::unique_lock<boost::shared_mutex> lock(mutex);  
            bucket_iterator const found_entry = find_entry_for(key);
            if (found_entry != data.end())
            {
                data.erase(found_entry);
            }
        }
    };

    //用unique_ptr来管理bucket,使得不能有多个指针管理一个桶。
    std::vector<std::unique_ptr<bucket_type> > buckets;  
    Hash hasher;

    /*
    * 根据哈希函数确定桶的位置
    */
    bucket_type& get_bucket(Key const& key) const 
    {
        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;
    
    //默认桶的数量默认为19,
    threadsafe_lookup_table(
        unsigned num_buckets = 19, Hash const& hasher_ = Hash()) :
        buckets(num_buckets), hasher(hasher_)
    {
        for (unsigned i = 0; i < num_buckets; ++i)
        {
            buckets[i].reset(new bucket_type);
        }
    }
    
    //删除拷贝成员
    threadsafe_lookup_table(threadsafe_lookup_table const& other) = delete;
    threadsafe_lookup_table& operator=(threadsafe_lookup_table const& other) = delete;
    
    /*
     * 更具key的值查找value
     */
    Value value_for(Key const& key,
        Value const& default_value = Value()) const
    {
        return get_bucket(key).value_for(key, default_value); // 8 
    }

    /*
     * 修改hash表的,插入或者更改
     */
    void add_or_update_mapping(Key const& key, Value const& value)
    {
        get_bucket(key).add_or_update_mapping(key, value); // 9 
    }

    /*
     * 删除元素
     */
    void remove_mapping(Key const& key)
    {
        get_bucket(key).remove_mapping(key); // 10 
    }

	/*
     * 用于备份map
     */
    std::map<Key, Value>  get_map() const
    {
        std::vector<std::unique_lock<boost::shared_mutex> > locks;
        for (unsigned i = 0; i < buckets.size(); ++i)
        {
            locks.push_back(
                std::unique_lock<boost::shared_mutex>(buckets[i].mutex));
        }
        std::map<Key, Value> res;
        for (unsigned i = 0; i < buckets.size(); ++i)
        {
            for (auto it = buckets[i].data.begin();
                it != buckets[i].data.end();
                ++it)
            {
                res.insert(*it);
            }
        }
        return res;
    }

};

线程安全的链表

template<typename T>
class threadsafe_list
{
    struct node // 内嵌节点类
    {
        std::mutex m;
        std::shared_ptr<T> data;
        std::unique_ptr<node> next;
        node() : // 2 
            next()
        {}
        node(T const& value) : // 3 
            data(std::make_shared<T>(value))
        {}
    };
    node head;
public:
    threadsafe_list()
    {}
    ~threadsafe_list()
    {
        remove_if([](node const&) {return true; });//删除所有节点
    }
    //禁止拷贝成员的出现
    threadsafe_list(threadsafe_list const& other) = delete;
    threadsafe_list& operator=(threadsafe_list const&
        other) = delete;
    //push_front,采用头插法,因此每次仅需要获取头结点锁即可
    //因为移除的操作需要获得头节点和下一个节点才会释放头节点锁,
    //因此,不会出插入时获取头节点后,下一个节点被删除的情况
    void push_front(T const& value)
    {
        std::unique_ptr<node> new_node(new node(value)); // 4 
        std::lock_guard<std::mutex> lk(head.m);
        new_node->next = std::move(head.next); // 5 
        head.next = std::move(new_node); // 6 
    }
  
    //传入谓词,对每个节点进行遍历。
    //每次需要获取下一个节点的锁,才能释放上一个节点的锁
    template<typename Function>
    void for_each(Function f) // 7 
    {
        node* current = &head;
        std::unique_lock<std::mutex> lk(head.m); // 8 
        while (node* const next = current->next.get()) // 9 
        {
            std::unique_lock<std::mutex> next_lk(next->m); // 10 
            lk.unlock(); // 11 
            f(*next->data); // 12 
            current = next;
            lk = std::move(next_lk)以上是关于C++并发编程----利用锁实现线程安全的数据结构(《C++ Concurrency in Action》 读书笔记)的主要内容,如果未能解决你的问题,请参考以下文章

C++并发编程----无锁实现线程安全队列(《C++ Concurrency in Action》 读书笔记)

C++并发编程----无锁实现线程安全队列(《C++ Concurrency in Action》 读书笔记)

并发编程2_synchronized锁

并发编程2_synchronized锁

并发编程2_synchronized锁

并发编程2_synchronized锁