スレッドの安全なstack
6576 ワード
stackは容器ではなくアダプタで、その実現は大体次のようなものです.
しかし、このバージョンはスレッドが安全ではありません.例えば、
このような場合、1つの要素しかないsに対して、スレッドAとスレッドBは現在ifでの判断を実行しており、このときスレッドAはvalueの値を得てpop()を実行する.しかし、その後、スレッドの切り替えが実行されると、スレッドBは師弟がvalueに値をつけようとしたが、すでに人は空になっていた.
では、スレッドが安全であることを前提にスタックトップの値を取得し、コピー後にpopするにはどうすればいいのでしょうか.答えはこうです.
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 ();
}
};