Let's see the case of std::stack. This class has been designed expressely to be used in monothreading code, and it can't be easily adapted to multithreading. The main issue is in how its top() and pop() functions are designed. If we call these methods on an empty stack we have an undefined behaviour, so we have to call the other member function empty() to ensure we can actually perform that operations.
Here is a couple of tests written for Google Test that show how top() and pop() are expected to be called:
TEST(StdStack, Top)
{
const int value = 42;
std::stack<int> si;
si.push(value);
if(si.empty() == false)
{
// *** [A] ***
ASSERT_FALSE(si.empty());
EXPECT_EQ(value, si.top());
}
}
TEST(StdStack, Pop)
{
const int value = 42;
std::stack<int> si;
si.push(value);
if(si.empty() == false)
{
// *** [A] ***
ASSERT_FALSE(si.empty());
si.pop();
EXPECT_TRUE(si.empty());
}
}
We expect both tests to succeeded. But what if we have two threads sharing the same stack container? What if in [A] the other thread butt in and pop() an element from the stack? It is easy to redesign the tests to emulate this behaviour, and we can see what is the result, in both case the subsequent assertions are bound to fail.
As we see, the problem is in how the std::stack interface is designed. Splitting the functionality in couple of methods, empty()/top() and empty()/pop(), makes it impossible to adapt it to a multithreading environment. We should provide a different interface where top() and pop() does not require a previous call to empty().
No comments:
Post a Comment