C++多线程学习---线程间的共享数据

多线程间的共享数据如果不加以约束是有问题的。最简单的方法就是对数据结构采用某种保护机制,通俗的表达就是:

确保只有进行修改的线程才能看到不变量被破坏时的中间状态。从其他访问线程的角度来看,修改不是已经完成了,就是还没开始。

1.使用互斥量保护共享数据

    当访问共享数据前,使用互斥量将相关数据锁住,再当访问结束后,再将数据解锁。C++标准库为互斥量提供了一个RAII语法的模板类std::lack_guard ,其会在构造的时候提供已锁的互斥量,并在析构的时候进行解锁.

#include <list>
#include <mutex>
#include <algorithm>
std::list<int> some_list; // 1
std::mutex some_mutex; // 2
void add_to_list(int new_value)
{
    std::lock_guard<std::mutex> guard(some_mutex); // 3
    some_list.push_back(new_value);
}
bool list_contains(int value_to_find)
{
    std::lock_guard<std::mutex> guard(some_mutex); // 4
    return std::find(some_list.begin(),some_list.end(),value_to_find) != some_list.end();
}
上述例子通过lock_guart<std::mutex>使得add_to_list和list_contains这两个函数对数据的访问是互斥的。从而保证多线程中数据的安全。


2.堤防接口内在的条件竞争

比如你在写一个栈的数据结构,并且给栈的push\pop\top\empty\size\等接口增加了互斥保护。看以下代码:

stack<int> s;
if (! s.empty()){ // 1
int const value = s.top(); // 2
s.pop(); // 3
do_something(value);
}
以上只是单线程安全,对于多线程在1和2之间,可能有来自另一个线程的pop()调用并删除了最后一个元素,此时就会出问题。

因为锁的粒度太小,需要保护的操作并未全覆盖到。可以考虑适当增大锁的粒度。

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();
}

3.堤防死锁问题

造成死锁最大的问题是:由两个或两个以上的互斥量来锁定一个操作。一对线程需要对他们所有的互斥量做一些操作,其中每个线程都有一个互斥量,且等待另一个解锁。这样没有线程能工作,因为他们都在等待对方释放互斥量。

避免死锁的一般建议,就是让两个互斥量总以相同的顺序上锁:总在互斥量B之前锁住互斥量A,可以有效防止大部分问题。一个更好的方法是利用C++提供的std::lock,可以一次性锁住多个互斥量。看以下例子:

class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X
{
private:
    some_big_object some_detail;
    std::mutex m;
public:
    X(some_big_object const& sd):some_detail(sd){}
    friend void swap(X& lhs, X& rhs)
    {
        if(&lhs==&rhs)
    	  return;
        std::lock(lhs.m,rhs.m); // 1
        std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock); // 2
        std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock); // 3
        swap(lhs.some_detail,rhs.some_detail);
     }
};
调用std::lock() ①锁住两个互斥量,并且两个std:lock_guard 实例已经创建好②③,还有一个互斥量。提供std::adopt_lock 参数除了表示std::lock_guard 对象已经上锁外,还表示现成的锁,而非尝试创建新的锁。

注意:一个互斥量可以在同一线程上多次上锁(std::recursive_mutex)。

以下是设计锁的几条忠告:

1.尽量避免嵌套锁,一个线程已获得锁时别再获取第二个

2.避免在持有锁是调用用户提供的代码,因为用户的代码不可预知,有发生死锁的可能

3.使用固定顺序获取锁--多于多个锁时可有效避免死锁

对于锁的粒度问题也是需要注意的,大的粒度可以保护更多的数据,但是其对性能的影响也越大。同时需要尽可能他将锁的持有时间减到最小。

比较操作符一次锁住一个互斥量:

friend bool operator==(Y const& lhs, Y const& rhs)
{
    if(&lhs==&rhs)
        return true;
    int const lhs_value=lhs.get_detail(); // 2
    int const rhs_value=rhs.get_detail(); // 3
    return lhs_value==rhs_value; // 4
}




利用int的拷贝来减少锁的等待时间,是一种高效的做法。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值