スレッドの安全なstack

6576 ワード

stackは容器ではなくアダプタで、その実現は大体次のようなものです.
template<typename T, typename Container = 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&&);

};

しかし、このバージョンはスレッドが安全ではありません.例えば、
void oops ()

{

    stack<int> s;

    if (!s.empty()) {

        int const value = s.top ();

        s.pop ();

        //do_something (value);

    }

}

このような場合、1つの要素しかないsに対して、スレッドAとスレッドBは現在ifでの判断を実行しており、このときスレッドAはvalueの値を得てpop()を実行する.しかし、その後、スレッドの切り替えが実行されると、スレッドBは師弟がvalueに値をつけようとしたが、すでに人は空になっていた.
では、スレッドが安全であることを前提にスタックトップの値を取得し、コピー後にpopするにはどうすればいいのでしょうか.答えはこうです.
template<typename Stack>

void empty_stack_check (const Stack& s)

{

    if (s.empty ()) {

        throw empty_stack ();

    }

}



struct empty_stack : std::exception

{

    const char* what () const throw();

};



template<typename T>

class thread_safe_stack

{

private:

    stack<T>      data;

    mutable mutex m;

public:

    using lockGuard = lock_guard<mutex>;

    thread_safe_stack () {}

    thread_safe_stack (const thread_safe_stack& other)

    {

        lockGuard lock (other.m);

        data = other.data;

    }



    stack& operator=(const thread_safe_stack&) = delete;



    void push (T new_value)

    {

        lockGuard lock (m);

        data.push (new_value);

    }



    shared_ptr<T> pop ()

    {

        lockGuard lock (m);

        empty_stack_check (data);



        shared_ptr<T> const res = 

            make_shared<T> (data.top());

        data.pop ();

        return res;

    }



    void pop (T& value)

    {

        lockGuard lock (m);

        empty_stack_check (data);

        value = data.top ();

        data.pop ();

    }



    bool empty () const

    {

        lockGuard lock (m);

        return data.empty ();

    }

};