Two boost::conditions on a std::stack

There is a glitch in the design of the code in the previous post: when the reader is faster than the writer some data risk to get lost.

That's the reason for the redesign of the solution we'll see here.

To solve this issue we'll introduce a second boost::condition and redesign accordingly the reading/writing functions.

Here is our new conditions, as private data member in class ReadWrite:

boost::condition cdEmpty_;
boost::condition cdNotEmpty_;

The first one will be used to notify that the stack is empty, the second that at least an item is in the stack.

The substantial change in the reader is that it now notify to the writer when it uses a last item in the stack, making it empty. Here is the function after the rewriting:

void reading()
{
while(true) // 1.
{
boost::mutex::scoped_lock ls(ms_);
if(s_.empty()) // 2.
{
message("wait for an item in the stack");
cdNotEmpty_.wait(ls);
}

int value = s_.top();
s_.pop();
if(s_.empty()) // 3.
{
message("stack is now empty");
cdEmpty_.notify_one();
}
ls.unlock();

if(value == TERMINATOR) // 4.
{
message("terminator found in the stack");
return;
}
else
{
message("read", value);
boost::this_thread::sleep(boost::posix_time::seconds(delayR_));
}
}
}

1. The loop goes on till the function returns in (2).
2. The reader expects something in the stack, nothing is there, so it waits on a condition that the stack status changes.
3. The reader has emptied the stack, we notify this through the condition cdEmpty_.
4. When the terminator is read, a special message, no waiting, and function termination is performed.

The writer changes accordingly. It writes all its data to the stack and then, when it finds that the stack has been emptied, sends the terminator:

void writing()
{
for(int i = 1; i < 6; ++i)
{
{
boost::lock_guard<boost::mutex> l(ms_);
s_.push(i);
}
message("write", i);
cdNotEmpty_.notify_one(); // 1.

boost::this_thread::sleep(boost::posix_time::seconds(delayW_));
}

// wait until stack is empty before sending the terminator
boost::mutex::scoped_lock ls(ms_);
if(s_.empty() == false)
{
message("stack is not empty");
cdEmpty_.wait(ls); // 2.
}

message("sending the terminator");
s_.push(TERMINATOR);
cdNotEmpty_.notify_one();
ls.unlock(); // 3.
}

1. Any time an item is pushed on the stack, we notify that to the other thread.
2. We found the stack was not empty, we wait till we get notified that.
3. This unlock it is not actually necessary, since the ls destructor would perform it, if required, when the local variable goes out of scope. But it won't hurt.

No comments:

Post a Comment