对于C++ 并发编程实战这本书的3.2.3中的内容进行一个总结:
首先是对互斥量保护变量的颗粒度进行探讨:
如果我们对链表中的每个节点用一个互斥量保护,并不会相邻的3个节点并发访问的问题,这个时候是我们控制颗粒太细腻导致的,所以我们用一个互斥量保护这个链表,使之整个整体只能由一个线程进行访问;
但这个时候仍然没有拜托全部的困境,另外的困境的本质是线程之间的竞争导致各个线程获取的信息是错误的,比如线程A先判断一个栈是否为空,如果为空进行工作,且这个工作仅仅只能在空栈上运行,这个时候在A判断语句返回true之后在进行空栈工作之前,线程B对栈中插入一个元素,这会导致线程A报错。
这也会导致被引用的栈元素的下一个元素得不到处理,且得不到任何反馈,更可怕的是可能栈中只有一个元素,这会导致我们去pop一个不存在的元素
我来解释一下这个是什么情况:
T pop(){
T temp = this->top;
//删除操作
return temp;
}
对于上述代码:有一个潜在的问题,这个问题与线程无关。这个函数中我们有两次拷贝,第一次是赋值运算符,第二次是return temp,当我们在赋值运算符调用拷贝构造函数时报错,这并不会导致另外的错误,因为删除操作是在其之后的,但如果在将temp拷贝到调用函数的栈内存的时候报错,这会导致被调用的栈错误,因为此刻我们已经见栈顶元素删除了,但获得的是一个错误信息;
为了解决这个问题,这本书提供了3个解决方案:
1:
2:
3:
下面提供了一些代码,展示了用一个互斥量保护整个栈以及解决上述问题的方案:
#include <exception>
#include <memory>
#include <mutex>
#include <stack>
struct empty_stack : std::exception
{
const char* what() const throw() {
return "empty stack!";
};
};
template<typename T>
class threadsafe_stack
{
private:
std::stack<T> data;
mutable std::mutex m;
public:
threadsafe_stack()
: data(std::stack<int>()) {}
threadsafe_stack(const threadsafe_stack& other)
{
std::lock_guard<std::mutex> lock(other.m);
data = other.data; // 1 在构造函数体中的执行拷贝
}
threadsafe_stack& operator=(const threadsafe_stack&) = delete;
void push(T new_value)
{
std::lock_guard<std::mutex> lock(m);
data.push(new_value);
}
std::shared_ptr<T> pop()
{
std::lock_guard<std::mutex> lock(m);
if (data.empty()) throw empty_stack(); // 在调用pop前,检查栈是否为空
std::shared_ptr<T> const res(std::make_shared<T>(data.top())); // 在修改堆栈前,分配
出返回值
data.pop();
return res;
}
void pop(T& value)
{
std::lock_guard<std::mutex> lock(m);
if (data.empty()) throw empty_stack();
value = data.top();
data.pop();
}
};
这个确实是这样的,如果是初始化列表,因为初始化列表是发生在函数调用之前的,所以没法用互斥量进行保护;
这里提及了互斥量保护的颗粒度太大的话,会导致多个线程竞争激烈,从而导致性能降低;