1.条件竞争
条件竞争一般是指两个及两个以上线程共享数据内存,并抢着完成各自的任务。线程在抢夺资源完成自己的任务时,可能会破坏其中共享数据的不变量,这种情况就是恶性条件竞争,这种破坏不变量的竞争应该尽量去避免。
2.恶性条件竞争避免
C++线程库中提供一种方法对共享数据进行保护,即互斥锁(std::mutex)。所谓互斥锁,实质上就是指当某一个线程拥有锁(即资源),在执行其线程任务时,其它的线程只能等待,只有当上一个线程完成自己的任务并且释放锁的资源后,其它的线程才能重新上锁(即获取资源),再完成自己的任务。
备注:互斥锁主要是用来进行同步操作。
3.RAII机制与互斥锁
一般情况下,互斥锁与RAII机制结合使用。C++线程库中提供了这样的用法,包含在头文件<mutex>里,即std::lock_guard,该类主要就是提供RAII机制。其实也就是当锁用完后,自动进行释放(解锁)。
如下图所示:
示例代码:
#include <iostream>
#include <mutex>
#include <algorithm>
#include <list>
#include <thread>
std::mutex m;
std::list<int> some_list;
bool res;
void pad(int new_value){
std::lock_guard<std::mutex> lk(m);
some_list.push_back(new_value);
}
bool query(int value){
std::lock_guard<std::mutex> lk(m);
res = std::find(some_list.begin(), some_list.end(), value) != some_list.end();
return res;
}
int main(int argc, char **argv){
std::thread p(pad, 5);
p.join();
std::cout << some_list.back() << std::endl;
std::thread q(query, 5);
q.join();
std::cout << res << std::endl;
return 0;
}
运行结果:
5
1
备注: 互斥锁也需要正确的使用。一般错误情况是将受保护的数据以指针或者引用的方式传到了互斥锁作用域之外,导致互斥锁形同虚设,并没有真正的保护数据。
4.接口条件竞争
接口条件竞争是指在定义的接口之间也存在着某一种竞争。比如栈的实现接口之间就存在着这样的竞争。
- top()与empty()之间:假设新线程在接口empty()与接口top()之间执行了pop()操作,这样可能会导致栈空的情况下出栈。
- top()与pop()之间:假设新线程在接口top()与接口pop()之间执行了top()操作,如果栈中有两个及以上的数据时,编译后,好像并没有太大的异常。但是我们仔细观察会发现,其实出栈的元素可能是同一个,这样的错误,是很难被发现的。
解决方案:
- 条件变量(同步)(解决top与empty的接口竞争)
- 重定义出栈接口,返回出栈元素的指针(智能指针)(解决top与pop之间竞争)
线程安全的栈
#include <stack>
#include <mutex>
#include <condition_variable>
#include <memory>
template<typename T>
class safed_thread_stack{
public:
explicit safed_thread_stack() noexcept {}
explicit safed_thread_stack(safed_thread_stack &other) noexcept{
std::lock_guard<std::mutex> g(other.m);
stk = other.stk;
}
safed_thread_stack &operator=(safed_thread_stack &other) = delete;
private:
mutable std::mutex m;
std::stack<T>stk;
std::condition_variable cond_var;
public:
void push(T new_value);
std::shared_ptr<T> try_pop();
std::shared_ptr<T> wait_pop();
bool try_pop(T &value);
void wait_pop(T &value);
bool empty() const;
};
template<typename T>
void safed_thread_stack<T>::push(T new_value){
std::lock_guard<std::mutex>g(m);
stk.push(new_value);
cond_var.notify_one();
}
template<typename T>
std::shared_ptr<T> safed_thread_stack<T>::try_pop(){
std::lock_guard<std::mutex> g(m);
if(stk.empty())
return std::shared_ptr<T>();
std::shared_ptr<T>ptr = std::make_shared<T>(stk.top());
stk.pop();
return ptr;
}
template<typename T>
std::shared_ptr<T> safed_thread_stack<T>::wait_pop(){
std::unique_lock<std::mutex> g(m);
cond_var.wait(g, [this](){
return !stk.empty();
});
std::shared_ptr<T>ptr = std::make_shared<T>(stk.top());
stk.pop();
return ptr;
}
template<typename T>
bool safed_thread_stack<T>::try_pop(T &value){
std::lock_guard<std::mutex> g(m);
if(stk.empty())
return false;
value = stk.top();
stk.pop();
return true;
}
template<typename T>
void safed_thread_stack<T>::wait_pop(T &value){
std::unique_lock<std::mutex> g(m);
cond_var.wait(g, [this](){
return !stk.empty();
});
value = stk.top();
stk.pop();
}
template<typename T>
bool safed_thread_stack<T>::empty() const{
std::lock_guard<std::mutex> g(m);
return stk.empty();
}
总结:互斥锁是用来保护共享数据的。