避免恶性条件竞争
双向链表具有前后两个链,当一个线程修改一个双向链表的前链还未来及修改后链时,另一个线程进行了数据访问,此时就会发生恶性条件竞争。多线程编程中,我们总会遇到多个线程对一个数据块进行修改,所以需要避免恶性条件竞争。避免恶性条件竞争有三个主要方法。
1、对数据块进行加锁等数据保护机制。同一时间,只有一个线程可对数据进行修改操作。
2、实现无锁化编程。对数据结构进行无锁化设计。
3、利用数据库中事务的思想,采用和事务一样的原理进行数据修改。
使用互斥量保护共享数据
互斥量是c++中最基本数据保护的方式。使用std::mutex来声明互斥量,使用lock()和unlock()函数进行加锁和解锁操作。因为使用lock()后,必须使用unlock()来解锁(包括异常情况),所以操作繁琐。我们可以利用RAII(获取资源即初始化)来完成这一操作。c++库提供了模板类std::lack_guard来完成在构造时加锁,析构时解锁的操作。两者都声明在<mutex>头文件。
#include <mutex>
std::mutex my_mutex;
void foo1(){
my_mutex.lock();
do_something();
my_mutex.unlock();
}
void foo2(){
std::lock_guard<std::mutex> my_guard(my_mutex);
do_something();
}
foo1和foo2都加了互斥量,对数据进行了保护。
注意:要避免将保护数据的引用或者指针作为返回值传出或者参数传入,这将导致保护数据在保护域外被使用。
typedef void (*funType) (int&);
class foo{
private:
int a = 0;
mutex my_mutex;
public:
void f(funType func){
std::lock_guard<mutex> my_guard(my_mutex);
func(a);
}
void show(){
cout << a <<endl;
}
};
int* unprotectd_data;
void func(int & a){
unprotectd_data = &a; //恶意或许a的地址
}
int main(){
foo a;
a.show();
a.f(func);
*unprotectd_data = 1; //a的地址不受保护,在互斥量保护外修改
a.show();
}
foo类的本意是只能在f函数内对变量a进行操作。但是当我们恶意地用指针来接收a变量的引用时,我们就可以在f的保护域外修改a。
互斥量范围大小与接口设计
假设我们有一个获取栈顶的函数pop
void pop(T & t){
if(!stack.empty())
t = stack.top();
stack.pop();
}
假设stack的每一个函数都是线程安全的,但是我们的pop函数线程安全的吗?答案肯定是不安全。调用empty()之后,别的线程可能