如何只保留最必要的串行操作,将串行操作减少至最低程度,并且最大限度地实现真正的并发访问?
1)队列,如果wait_and_pop()产生异常(如新指针shared_ptr<>在构建时就有可能产生异常),就不会有新线程被唤醒。
解决:将shared_ptr<>的初始化移到push()调用处,令队列改为存储shared_ptr<>,从内部复制shared_ptr<>实例的操作不会抛出异常,所以wait_and_pop()异常安全。此外,shared_ptr<>实例内存分配可以脱离锁保护。
/*
线程间传递数据,队列,条件变量控制同步
条件变量的wait和unique_lock<>一起使用,而不和lock_guard<>一起使用,因为要灵活解锁
*/
template<typename T>
class threadsafe_queue
{
queue<shared_ptr<T>> data;
mutable mutex m;
condition_variable cond;
public:
threadsafe_queue(){}
threadsafe_queue(const threadsafe_queue& other)
{
scoped_lock lock(m);
data = other.data;
}
threadsafe_queue& operator=(const threadsafe_queue&) = delete;
void push(T new_data)
{
shared_ptr<T> newdata(make_shared<T>(move(new_data)));
scoped_lock lock(m);
data.push(newdata);
cond.notify_one();
}
bool try_pop(T& value)
{
scoped_lock lock(m);
if (data.empty())
return false;
value = move(*data.front());
data.pop();
return true;
}
shared_ptr<T> try_pop()
{
scoped_lock lock(m);
if (data.empty())
return shared_ptr<T>();
shared_ptr<T> res = data.front();
data.pop();
return res;
}
void wait_and_pop(T& value)
{
unique_lock lock(m);
cond.wait(lock, [this] {return !data.empty(); });
value = move(*data.front());
data.pop();
}
shared_ptr<T> wait_and_pop()
{
unique_lock lock(m);
cond.wait(lock, [this] {return !data.empty(); });
shared_ptr<T> res = data.front();
data.pop();
return res;
}
bool empty() const
{
scoped_lock lock(m);
return data.empty();
}
};
2)使用单链表,带有精细粒度锁的线程安全队列
1.预先设立一个不含数据的虚位节点,这样head->next和tail->next不会出现竞争,即没有可能指向同一节点。此外,push()可以只操作tail指针。但是,为了容纳虚位节点,需通过指针间接存储数据,额外增加一个访问级别。
2.get_tail()调用在互斥head_mutex的保护内,保证了head指针不可能越过get_tail()所返回的位置,不变量保持成立。
3.try_pop()只在互斥tail_mutex短暂持锁,以保护tail指针读取,所以try_pop()整个调用过程几乎都可以与push()并发执行。队列节点通过unique_ptr<>的析构函数删除,操作开销大,在互斥外执行。
4.push()的新节点和新数据的内存分配脱离锁保护。
/*
使用单链表,带有精细粒度锁的线程安全队列,支持等待功能
wait_for_data()等待数据加入空队列,以将其弹出。返回锁,保证了头结点弹出的全过程都持有同一个锁
*/
template<typename T>
class threadsafel_queue
{
private:
struct node
{
shared_ptr<T> data;
unique_ptr<node> next;
};
unique_ptr<node>head;
node* tail;
mutex head_m;
mutex tail_m;
condition_variable cond;
node* get_tail()
{
scoped_lock tail_lock(tail_m);
return tail;
}
unique_ptr<node> pop_head()
{
unique_ptr<node>old_head = move(head);
head = move(old_head->next);
return old_head;
}
unique_lock<mutex> wait_for_data()
{
unique_lock head_lock(head_m);
cond.wait(head_lock, [this] {return head.get() != get_tail(); });
return move(head_lock);
}
unique_ptr<node> wait_pop_head()
{
unique_lock head_lock(wait_for_data());
return pop_head();
}
unique_ptr<node> wait_pop_head(T& value)
{
unique_lock head_lock(wait_for_data());
value = move(*head->data);
return pop_head();
}
unique_ptr<node> try_pop_head()
{
scoped_lock head_lock(head_m);
if (head.get() == get_tail())
{
return unique_ptr<node>();
}
return pop_head();
}
unique_ptr<node> try_pop_head(T & value)
{
scoped_lock head_lock(head_m);
if (head.get() == get_tail())
{
return unique_ptr<node>();
}
value = move(*head->data);
return pop_head();
}
public:
threadsafel_queue() :head(new node), tail(head.get()) {}
~threadsafel_queue()
{
delete tail;
}
threadsafel_queue(const threadsafel_queue& other) = delete;
threadsafel_queue& operator=(const threadsafel_queue& other) = delete;
shared_ptr<T>wait_and_pop()
{
return wait_pop_head()->data;
}
void wait_and_pop(T & value)
{
wait_pop_head(value);
}
shared_ptr<T> try_pop()
{
unique_ptr<node>old_head = try_pop_head();
return old_head ? old_head->data : shared_ptr<T>();
}
bool try_pop(T& value)
{
return try_pop_head(value);
}
void push(T new_value)
{
shared_ptr<T> new_data(make_shared<T>(move(new_value)));
unique_ptr<node>p(new node);
node* new_tail = p.get();
{
scoped_lock tail_lock(tail_m);
tail->data = new_data;
tail->next = move(p);
tail = new_tail;
}
cond.notify_one();
}
bool empty()
{
scoped_lock head_lock(head_m);
return (head.get() == get_tail());
}
};
3)使用散列表,带有精细粒度锁的线程安全查找表
1.每个键属于一个桶,可以为每个桶使用独立的锁。
2.再使用共享锁,支持多个读线程或一个写线程。
3.数据快照,以快照形式获取查找表的当前状态,并保存为map<>,该功能所有桶被锁住。
坑:find_entry_for()函数后加了const,那data.begin()和data.end()返回const_iterator,进而find_if返回const_iterator,无法转换为bucket_iterator。即不能把const迭代器转为非const迭代器。
解决:find_entry_for重载,一个加const,用于value_for(),一个不加const用于add_or_update_mapping()和remove_mapping()。
注意:const成员函数,要用mutable。scoped_lock删去了拷贝构造,要用unique_lock。
/*
使用散列表,带有精细粒度锁的线程安全查找表
*/
template<typename Key,typename Value,typename Hash=hash<Key>>
class threadsafe_lookup_table
{
private:
class bucket_type
{
public:
typedef pair<Key, Value> bucket_value;
typedef list<bucket_value> bucket_data;
using bucket_iterator = typename bucket_data::iterator;
using bucket_const_iterator = typename bucket_data::const_iterator;
bucket_data data;
mutable shared_mutex m;
bucket_const_iterator find_entry_for(const Key& key) const
{
return find_if(data.begin(), data.end(), [&](const bucket_value& item) {return item.first == key; });
}
bucket_iterator find_entry_for(const Key& key)
{
return find_if(data.begin(), data.end(), [&](const bucket_value& item) {return item.first == key; });
}
public:
Value value_for(const Key& key, const Value& dafault_value) const
{
shared_lock lock(m);
bucket_const_iterator found_entry=find_entry_for(key);
return (found_entry == data.end()) ? dafault_value : found_entry->second;
}
void add_or_update_mapping(const Key& key, const Value& value)
{
scoped_lock lock(m);
bucket_iterator 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(const Key& key)
{
scoped_lock lock(m);
bucket_iterator found_entry = find_entry_for(key);
if (found_entry != data.end())
data.erase(found_entry);
}
};
vector<unique_ptr<bucket_type>> buckets;
Hash hasher;
bucket_type& get_bucket(const Key& key) const
{
const size_t bucket_index = hasher(key) % buckets.size();
return *buckets[bucket_index];
}
public:
threadsafe_lookup_table(unsigned num_bucket = 19) :buckets(num_bucket),hasher(Hash())
{
for (unsigned i=0;i<num_bucket;++i)
{
buckets[i].reset(new bucket_type);
}
}
threadsafe_lookup_table(const threadsafe_lookup_table& other) = delete;
threadsafe_lookup_table& operator=(const threadsafe_lookup_table& other) = delete;
Value value_for(const Key& key, const Value& dafault_value) const
{
return get_bucket(key).value_for(key, dafault_value);
}
void add_or_update_mapping(const Key& key, const Value& value)
{
get_bucket(key).add_or_update_mapping(key, value);
}
void remove_mapping(const Key& key)
{
get_bucket(key).remove_mapping(key);
}
map<Key, Value> get_map() const
{
vector<unique_lock<shared_mutex>>locks;
for (unsigned i=0;i<buckets.size();++i)
{
locks.push_back(unique_lock(buckets[i]->m));
}
map<Key, Value>res;
for (unsigned i = 0; i < buckets.size(); ++i)
{
for (auto it:buckets[i]->data)
res.insert(it);
}
return res;
}
};
4)带有精细粒度锁的线程安全链表,支持迭代功能
1.每个节点都具备自己的互斥,可以在链表不同部分执行真正的并发操作:每个操作仅需锁住目标节点,当操作转移到下一个目标节点,原来的锁即可解开。
2.使用unique_ptr<>,出了作用域,指针自动销毁,同时连带删除目标节点。
3.remove_if中,销毁行为在解锁后发生,否则销毁已锁互斥是未定义行为。
/*
带有精细粒度锁的线程安全链表,支持迭代功能
*/
template<typename T>
class threadsafe_list
{
private:
struct node
{
mutex m;
shared_ptr<T> data;
unique_ptr<node>next;
node():next(){}
node(const T& value):data(make_shared<T>(move(value))){}
};
unique_ptr<node> head;
public:
threadsafe_list():head(new node){}
threadsafe_list(const threadsafe_list& other) = delete;
threadsafe_list& operator=(const threadsafe_list& other) = delete;
void push_front(const T& value)
{
unique_ptr<node>new_node(new node(value));
scoped_lock lock(head->m);
new_node->next = move(head->next);
head->next = move(new_node);
}
template<typename Function>
void for_each(Function f)
{
auto current = head.get();
unique_lock lock(head->m);
while (auto next = current->next.get())
{
unique_lock next_lock(next->m);
lock.unlock();
f(*next->data);
current = next;
lock = move(next_lock);
}
}
template<typename Predicate>
shared_ptr<T> find_first_if(Predicate p)
{
auto current = head.get();
unique_lock lock(head->m);
while (auto next = current->next.get())
{
unique_lock next_lock(next->m);
lock.unlock();
if (p(*next->data))
{
return next->data;
}
current = next;
lock = move(next_lock);
}
return shared_ptr<T>();
}
template<typename Predicate>
void remove_if(Predicate p)
{
auto current = head.get();
unique_lock lock(head->m);
while (auto next = current->next.get())
{
unique_lock next_lock(next->m);
if (p(*next->data))
{
unique_ptr<node> remove_node = move(current->next);
current->next = move(remove_node->next);
next_lock.unlock();
}
else
{
lock.unlock();
current = next;
lock = move(next_lock);
}
}
}
};