生产者和使用者函数,用于在操作手册中测试C++并发的线程安全堆栈示例

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

本文关键字:并发 C++ 线程 安全 堆栈 测试 函数 使用者 用于 操作手册 生产者      更新时间:2023-10-16

我已经开始学习并发(C++11),阅读《C++并发在行动》一书。如何测试线程安全堆栈类(示例取自操作清单 3.5 中的C++并发)。我希望对生产者/消费者函数有不同的实现,让我测试它的所有函数。

#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;
}

你只需要:

  • 从主函数创建堆栈
  • 启动一个将填充堆栈的线程(将堆栈对象指针作为参数传递给线程,并通过一直调用 push 使线程执行填充堆栈的 for 循环)
  • 然后,当此线程运行时,从主程序的另一个循环中清空堆栈

如果您只想进行快速测试并且不知道如何在创建时将对象传递给线程,您也可以将堆栈声明为全局变量。

如果您需要干净退出,请添加一个传递给线程的atomic(编辑,我首先建议volatile)bool,告诉它您已完成并要求它停止循环。然后使用 join 等待线程退出。

结构的最小测试驱动程序可能如下所示:

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;
}

请注意,这不会测试所有不同的函数,也不会测试所有可能的竞争条件,因此您必须扩展它。

关于课程设计的两点建议:

1) 当堆栈为空时,我不会抛出异常,因为这在异步场景中非常常见。而是让使用者线程等待(请参阅条件变量)或分别返回 false 或 nullptr。

2)在pop()函数中使用std::unique_ptr而不是std::shared_ptr<T>,因为它更有效率,而且您无论如何都不会在这里共享任何内容。