6.1 为并发设计的含义是什么
为并发设计数据结构是为了多个线程可以同时更好的使用此结构
设计并发数据结构的准则是什么
- 保证党数据结构不变性被别的线程破坏时的状态不被任何别的线程看到。——意味着当要在并发中修改此结构中的部分数据时(即数据结构的不变性被破坏),应当通过添加互斥元的方法阻止其他访问该数据的线程访问(破坏的状态不被别的线程看到)。
- 注意避免数据结构接口所固有的竞争现象,通过完整操作提供函数,而不是提供操作步骤。——在并发数据结构中使用某个操作的时候,应当注意由于数据的竞争性的原因,应当采用通过某个函数内部使用互斥元和操作的方式返回所要达到的目标,将其作为一个完成的整体来设计。
- 注意当出现例外时,数据结构时怎么来保证不变性不被破坏的。——在并发访问数据结构时,如何才能做到保证数据结构内的数据不变性不被破坏。
- 当使用数据结构时,通过限制锁的范围和避免使用嵌套锁,来降低产生死锁的机会。——由于在设计并发访问的数据结构中难免要添加互斥元,这种情况下,应该注意使用互斥元的范围(避免因为适用范围不当造成多线程并发效率低下(长时间霸占锁),或者造成数据还未达到想要操作的要求就释放的危险(锁定时间不够))。
6.2 基于锁的并发数据结构
在设计并发数据结构时最主要的地方是应当注意到互斥元的使用位置。
6.2.1 使用锁的线程安全栈
最简单的方法就是在原始栈的每个函数内部开头添加一个互斥元,可以达到安全并发访问此数据结构的目的,但是这种情况下效率较低。
#include <mutex> #include <stack> #include <memory> #include <exception> using namespace std; template<typename T> class safe_stack{ private: stack<T> sk; mutex m_mutex; public: safe_stack(){} safe_stack(int n, T value){ lock_guard<mutex> lg(m_mutex); while(n --) sk.push(value); } safe_stack(safe_stack& other){ lock_guard<mutex> lg(m_mutex); sk = other.sk; } safe_stack& operator=( safe_stack &other) = delete; void push(T v){ lock_guard<mutex> lg(m_mutex); sk.push(v); } T pop(){ lock_guard<mutex> lg(m_mutex); if(sk.empty()) throw empty_stack(); T ret = sk.top(); tk.pop(); return ret; } bool empty(){ lock_guard<mutex> lg(m_mutex); return sk.empty(); } };
6.2.2 使用锁和条件变量的线程安全队列
6.2.3 使用细粒度锁和条件变量的线程安全队列
1.通过分离数据允许并发
在链表的表头和表尾分别进行操作的时候可以用俩个互斥元来操作,这样可以提升并发的速率。
2.等待一个数据项pop
6.3 设计更复杂的基于锁的数据结构
6.3.1 编写一个使用锁的线程安全查找表
6.3.2 编写一个使用锁的线程安全链表
//相关代码有时间再补