sergio campo
sergio campo

Reputation: 276

Producer and consumer functions for test thread-safe stack examples of C++ concurrency in action book

I've started to learn concurrency(C++11) reading the book C++ Concurrency in Action. How to test a thread-safe stack class (Example was taken from C++ concurrency in action listing 3.5). I would like to have differents implementations of producer/consumer functions that let me test all its functions.

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

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

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()
{

  //test class 

 return 0;
}

Upvotes: 1

Views: 534

Answers (2)

MikeMB
MikeMB

Reputation: 21156

A minimal testdriver for your structure could look like this:

struct Msg {
    size_t a;size_t b;size_t c;size_t d;
};

bool isCorrupted(const Msg& m) {
    return !(m.a == m.b && m.b == m.c && m.c == m.d);
}

int main()
{
    threadsafe_stack<Msg> stack;

    auto prod = std::async(std::launch::async, [&]() {
        for (size_t i = 0; i < 1000000; ++i){
            Msg m = { i, i, i, i };
            stack.push(m);
            //std::this_thread::sleep_for(std::chrono::microseconds(1));
            if (i % 1000 == 0) {
                std::cout << "stack.push called " << i << " times " << std::endl;
            }
        }
    });

    auto cons = std::async(std::launch::async, [&]() {
        for (size_t i = 0; i < 1000000; ++i){
            try {
                Msg m;
                stack.pop(m);
                if (isCorrupted(m)) {
                    std::cout << i <<" ERROR: MESSAGE WAS CORRUPED:" << m.a << "-" << m.b << "-" << m.c << "-" << m.d << std::endl;
                }
                if (i % 1000 == 0) {
                    std::cout << "stack.pop called " << i << " times " << std::endl;
                }
            }
            catch (empty_stack e) {
                std::cout << i << " Stack was empty!" << std::endl;
            }
        }
    });

    prod.wait();
    cons.wait();

    return 0;
}

Note, that this doesn't test all different functions, nor for all possible race conditions, so you'd have to exend it.

Two recommendations regarding your class design:

1) I wouldn't throw an exception when the stack is empty, as this is a very common case in an asynchronous scenario. Rather make the consumer thread wait (see condition variables for this) or return a false or nullptr respectively.

2) Use std::unique_ptr instead of std::shared_ptr<T> in your pop() function as it is more efficient and you don't share anything here anyway.

Upvotes: 0

jpo38
jpo38

Reputation: 21514

You simply need to:

  • Create a stack from your main function
  • Start a thread that will fill the stack (pass the stack object pointer as parameter to the thread and make the thread execute a for loop filling the stack by calling push all the time)
  • Then, while this thread runs, empty the stack from another loop of your main program

You can also declare the stack as a global variable if you simply want to do a quick test and don't know how to pass objects to the thread upon creation.

If you need clean exit, add an atomic (edited, I first recommended volatile) bool passed to the thread to tell it you're done and ask it to stop its loop. Then use join to wait for the thread to exit.

Upvotes: 1

Related Questions