研究了一下线程安全的数据结构,参考了 《c++并发编程实战》这本书上的代码写了一个能够编译运行的版本
这份代码解决的核心问题是在细粒度锁的并发情况下可能出现的多个锁同时锁住一个节点
解决的方法是使用傀儡节点即在队列为“空”的情况下依然有两个节点一个为空的头节点一个为尾的空的傀儡节点
实现代码:
/*
* wait_lock_thread_queue.cpp
*
* Created on: Apr 28, 2018
* Author: clh01s@163.com
* 使用锁和等待以及实现细粒度锁
* 的线程安全队列
* 而且通过添加傀儡节点来解决队列
* 在并发模式下只有一个节点时
* push和pop操作时要用两个
* 互斥元同时锁住一个节点的
* 问题。
*/
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <future>
template<typename T>
class threadsafe_queue
{
public:
threadsafe_queue():
//为了在一开始就有一个傀儡节点可以使用,创建一个新的空节点
_head(new node),_tail(_head.get())
{}
threadsafe_queue(const threadsafe_queue& other)=delete;
threadsafe_queue& operator=(const threadsafe_queue& othre)=delete;
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;
}
std::shared_ptr<T> wait_and_pop()
{
std::unique_ptr<node> const old_head = wait_pop_head();
std::cout<<"old_head->data = "<<*old_head->data<<std::endl;
return old_head->data;
}
void wait_and_pop(T& value)
{
std::unique_ptr<node> const old_head = wait_pop_head(value);
}
void push(T new_value)
{
/*
* 创建一个T的新的实例
* 并且在shared_ptr<>中取得所有权
* 使用make_shared是为了避免第二次为引用计数分配内存的开销
*/
std::shared_ptr<T> new_data(
std::make_shared<T>(std::move(new_value)));
//创建的新的节点将会作为新的傀儡节点,因此无需向构造函数提供new_value
std::unique_ptr<node> p(new node);
//加锁
std::lock_guard<std::mutex> tail_lock(_tail_mutex);
//将旧的傀儡节点上的数据设置为新分配的new_value的副本
_tail->data = new_data;
node* const new_tail = p.get();
_tail->next = std::move(p);
_tail = new_tail;
_data_cond.notify_one();
}
bool empty()
{
std::lock_guard<std::mutex> head_lock(_head_mutex);
return (_head.get() == get_tail());
}
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;
node* _tail;
std::condition_variable _data_cond;
private:
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;
}
//确保数据被相关的wait_pop_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;
}
};
int main()
{
threadsafe_queue<int> my_queue;
int c = 0;
//创建线程时使用lambda
std::thread d(
[&]{
int d = 0;
for(;;)
{
if(!my_queue.empty())
{
d++;
if((d%10) == 0)
{
std::cout<<"线程d:d 求余10=0 开始删除一个队列元素 d = "<<d<<std::endl;
std::cout<<"线程d pop的值是:"<<*my_queue.wait_and_pop()<<std::endl;
c++;
if(c > 70)
{
return;
}
}
}
}
}
);
std::thread d2(
[&]{
int d = 0;
for(;;)
{
if(!my_queue.empty())
{
d++;
if((d%10) == 0)
{
std::cout<<"线程d2:d 求余10=0 开始删除一个队列元素 d = "<<(int)(d)<<std::endl;
std::cout<<"线程d2 pop的值是:"<<*my_queue.wait_and_pop()<<std::endl;
c++;
if(c > 70)
{
return;
}
}
}
}
}
);
for(int i = 0;i<3;++i)
{
std::thread t(
[&]{
int j;
for(;;)
{
// std::cout<<"线程id:"<<i<<"push "<<j<<std::endl;
++j;
my_queue.push(j);
}
});
t.detach();
}
d.join();
d2.join();
return 0;
}
执行:g++ file_name.cpp -pthread -std=c++11编译后执行