码迷,mamicode.com
首页 > 编程语言 > 详细

[C++11 并发编程] 06 Mutex race condition

时间:2015-08-14 22:47:55      阅读:155      评论:0      收藏:0      [点我收藏+]

标签:c++11   并发   mutex   

上一节中介绍了mutex的基本使用方法,使用mutex来保护共享数据并不能解决race condition带来的问题,假如我们有一个堆栈数据结构类似于std::stack它提供了5个基本操作push(),pop(),top(),empty(),和size()。这里的top()操作返回栈顶元素的拷贝,这样我们就可以使用一个mutex来保护栈内部的数据。但是race codition情况下,虽然使用mutex在stack的每个接口内都对共享数据进行了保护,仍然有问题存在。

#include <deque>
template<typename T,typename Container=std::deque<T> >
class stack
{
public:
    explicit stack(const Container&);
    explicit stack(Container&& = Container());
    template <class Alloc> explicit stack(const Alloc&);
    template <class Alloc> stack(const Container&, const Alloc&);
    template <class Alloc> stack(Container&&, const Alloc&);
    template <class Alloc> stack(stack&&, const Alloc&);

    bool empty() const;
    size_t size() const;
    T& top();
    T const& top() const;
    void push(T const&);
    void push(T&&);
    void pop();
    void swap(stack&&);
};

int main()
{}

这里的问题在于,empty()和size()的返回值是不可靠的,虽然在我们调用这两个函数时,它们的返回值是正确的,但是一旦返回,其它的线程就可以访问stack并push()新的数据到堆栈中,基于empty()和size()之前的返回值就可能导致问题。比如如下的情况,线程A和线程B获取了同一个栈顶数据的拷贝,线程A执行pop()将其弹出,线程B执行,就将栈顶之下第二个数据直接弹出了。这样导致这个数据并没有得到处理。

技术分享

因此,我们需要对接口进行重构。这里采用的方案是提供两种pop()接口,一种接受数据的引用,在pop()内将栈顶数据赋值给这个引用参数。另一个实现则是返回指向栈顶数据的指针。第一种接口大多数情况下是可行的,但是缺点是使用者需要先构造一个数据对象的实例,还要求这个对象是可以赋值的。第二种接口则不需要以传值的方式返回数据,但使用者要注意对指针的使用以避免内存泄露等问题。

重构后的stack头文件如下:

#include <exception>
#include <memory>

struct empty_stack: std::exception
{
    const char* what() const throw();
};

template<typename T>
class threadsafe_stack
{
public:
    threadsafe_stack();
    threadsafe_stack(const threadsafe_stack&);
    threadsafe_stack& operator=(const threadsafe_stack&) = delete;

    void push(T new_value);
    std::shared_ptr<T> pop();
    void pop(T& value);
    bool empty() const;
};

int main()
{}
由于stack不支持赋值操作,因此将其定义为delete。

stack的实现如下:

#include <exception>
#include <stack>
#include <mutex>
#include <memory>

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(){}
    threadsafe_stack(const threadsafe_stack& other)
    {
        std::lock_guard<std::mutex> lock(other.m);
        data=other.data;
    }
    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();
        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();
    }
    bool empty() const
    {
        std::lock_guard<std::mutex> lock(m);
        return data.empty();
    }
};

int main()
{
    threadsafe_stack<int> si;
    si.push(5);
    si.pop();
    if(!si.empty())
    {
        int x;
        si.pop(x);
    }
    
}
堆栈可以被拷贝,在拷贝构造函数中,使用mutex来对内部数据进行保护。为了保证内部数据被mutex保护,不能使用初始化参数列表来初始化堆栈的成员变量。

使用mutex要注意粒度问题,保护的粒度太小,会漏掉一些场景导致race condition。保护粒度太大则会降低并发线程的执行效率。要达到粒度适当,则可能需要多个mutex,使用多个mutex又有可能导致死锁问题。下一节,我们再看看死锁是怎么回事,以及怎么解决死锁问题。




版权声明:本文为博主原创文章,未经博主允许不得转载。

[C++11 并发编程] 06 Mutex race condition

标签:c++11   并发   mutex   

原文地址:http://blog.csdn.net/yamingwu/article/details/47423257

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!