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》 读书笔记)