超越99%的程序员,chatgpt用C++写个线程安全无锁环形队列

news2024/11/25 20:44:57

​肝了一个周末,我们一起来欣赏chatgpt写的代码吧。让其用C++为我们写一个线程安全的环形队列,并逐步提出一些需求。

先看带锁的实现。

带锁版本

我对chatgpt的需求是:

用C++实现线程安全的环形队列,提供代码注释,以及gtest单元测试和相应的测试报告。要求如下: 
1、环形队列要支持阻塞读和非阻塞读,写也一样 
2、存放的数据类型可以自定义 
3、代码风格使用google C++风格 
4、支持左右值写入 
5、高性能

高性能本意是让其提供一个无锁的实现,但似乎没有get到。

以下是chatgpt用C++实现的线程安全的环形队列,同时添加了gtest单元测试,并提供了相应的测试报告。

circular_queue.h

// 头文件防卫
#ifndef CIRCULAR_QUEUE_H
#define CIRCULAR_QUEUE_H

#include <mutex> // 互斥量
#include <condition_variable> // 条件变量

template <typename T>
class CircularQueue {
public:
    // 构造函数,初始化成员变量
    explicit CircularQueue(size_t capacity) :
        capacity_(capacity),
        size_(0),
        head_(0),
        tail_(0),
        buffer_(new T[capacity]) {}

    // 析构函数,释放 buffer_ 内存
    ~CircularQueue() {
        delete[] buffer_;
    }

    // 判断队列是否为空
    bool empty() {
        std::unique_lock<std::mutex> lock(mutex_);
        return size_ == 0;
    }

    // 判断队列是否已满
    bool full() {
        std::unique_lock<std::mutex> lock(mutex_);
        return size_ == capacity_;
    }

    // 获取队列中元素的数量
    size_t size() {
        std::unique_lock<std::mutex> lock(mutex_);
        return size_;
    }

    // 获取队列的容量
    size_t capacity() {
        return capacity_;
    }

    // 将元素加入队列,可能会阻塞
    bool push(const T& value, bool block = true) {
        std::unique_lock<std::mutex> lock(mutex_);

        if (block) {
            // 如果队列已满,则等待队列不满
            while (size_ == capacity_) {
                not_full_.wait(lock);
            }
        } else {
            // 如果队列已满,则返回 false
            if (size_ == capacity_) {
                return false;
            }
        }

        // 将元素加入队列尾部,并更新 tail_ 和 size_
        buffer_[tail_] = value;
        tail_ = (tail_ + 1) % capacity_;
        ++size_;

        // 通知一个等待在 not_empty_ 条件变量上的线程
        not_empty_.notify_one();

        return true;
    }

    // 将元素加入队列,可能会阻塞,使用右值引用
    bool push(T&& value, bool block = true) {
        std::unique_lock<std::mutex> lock(mutex_);

        if (block) {
            // 如果队列已满,则等待队列不满
            while (size_ == capacity_) {
                not_full_.wait(lock);
            }
        } else {
            // 如果队列已满,则返回 false
            if (size_ == capacity_) {
                return false;
            }
        }

        // 将元素加入队列尾部,并更新 tail_ 和 size_
        buffer_[tail_] = std::move(value);
        tail_ = (tail_ + 1) % capacity_;
        ++size_;

        // 通知一个等待在 not_empty_ 条件变量上的线程
        not_empty_.notify_one();

        return true;
    }

    // 从队列中取出元素,可能会阻塞
    bool pop(T& value, bool block = true) {
        std::unique_lock<std::mutex> lock(mutex_);

        if (block) {
            // 如果队列为空,则等待队列不空
            while (size_ == 0) {
                not_empty_.wait(lock);
            }
        } else {
            // 如果队列为空,则返回 false
            if (size_ == 0) {
                return false;
            }
        }

        // 取出队列头部元素,并更新 head_ 和 size_
        value = std::move(buffer_[head_]);
        head_ = (head_ + 1) % capacity_;
        --size_;

        // 通知一个等待在 not_full_ 条件变量上的线程
        not_full_.notify_one();

        return true;
    }

private:
    const size_t capacity_; // 队列容量
    size_t size_; // 队列中元素的数量
    size_t head_; // 队列头部指针
    size_t tail_; // 队列尾部指针
    T* buffer_; // 队列缓冲区    
    std::mutex mutex_; // 互斥量,保护队列缓冲区和队列大小
    std::condition_variable not_full_; // 条件变量,当队列满时等待
    std::condition_variable not_empty_; // 条件变量,当队列空时等待
};

#endif // CIRCULAR_QUEUE_H

针对上面的代码,质量挺好,上面的注释不是我写的,push和pop接口不指定第二个参数的话,默认是阻塞的,这一点使用时需要注意。

生成的单元测试不全面,我又贴了它生成的代码,让他帮我生成单元测试,这次很不错,接口基本覆盖了,还知道起线程测试阻塞接口,左右值的插入版本都测了。

相关视频推荐

高并发场景3种锁方案:自旋锁、互斥锁、原子操作的优缺点

高性能服务器开发必备组件-无锁队列的设计与实现

线程池、内存池、异步请求池、数据库连接池、无锁队列的ringbuffer,提升程序性能必备技术

免费学习地址:c/c++ linux服务器开发/后台架构师

需要C/C++ Linux服务器架构师学习资料加qun812855908(群内已接入chatgpt)获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

测试时发现了单元测试有bug:

  1. 调用空队列的pop接口时未指明非阻塞,结果把自己阻塞住了,见单元测试注释(1)

可见目前为止chatgpt写出来的代码也是有bug的,不能全盘依赖。

circular_queue_gtest.cpp

以下是CircularQueue类的单元测试示例代码:

#include <gtest/gtest.h>
#include <thread>
  
#include "circular_queue.h"

TEST(CircularQueueTest, EmptyQueue) {
    CircularQueue<int> queue(10);
    ASSERT_TRUE(queue.empty());
    ASSERT_FALSE(queue.full());
    ASSERT_EQ(queue.size(), 0);
    ASSERT_EQ(queue.capacity(), 10);
}

TEST(CircularQueueTest, PushAndPop) {
    CircularQueue<int> queue(3);

    ASSERT_TRUE(queue.push(1));
    ASSERT_EQ(queue.size(), 1);
    ASSERT_FALSE(queue.empty());
    ASSERT_FALSE(queue.full());

    ASSERT_TRUE(queue.push(2));
    ASSERT_EQ(queue.size(), 2);
    ASSERT_FALSE(queue.empty());
    ASSERT_FALSE(queue.full());

    ASSERT_TRUE(queue.push(3));
    ASSERT_EQ(queue.size(), 3);
    ASSERT_FALSE(queue.empty());
    ASSERT_TRUE(queue.full());

    int value;
    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, 1);
    ASSERT_EQ(queue.size(), 2);
    ASSERT_FALSE(queue.empty());
    ASSERT_FALSE(queue.full());

    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, 2);
    ASSERT_EQ(queue.size(), 1);
    ASSERT_FALSE(queue.empty());
    ASSERT_FALSE(queue.full());

    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, 3);
    ASSERT_EQ(queue.size(), 0);
    ASSERT_TRUE(queue.empty());
    ASSERT_FALSE(queue.full());

    ASSERT_FALSE(queue.pop(value,false)); // (1) 此项测试未指定非阻塞,笔者修改了
}

TEST(CircularQueueTest, PushAndPopWithBlocking) {
    CircularQueue<int> queue(2);

    std::thread t([&queue]() {
        int value = 0;
        queue.pop(value);
        ASSERT_EQ(value, 1);
        queue.pop(value);
        ASSERT_EQ(value, 2);
    });

    ASSERT_TRUE(queue.push(1));
    ASSERT_TRUE(queue.push(2));
    ASSERT_TRUE(queue.push(3));

    t.join();
}

TEST(CircularQueueTest, PushAndPopWithNonBlocking) {
    CircularQueue<int> queue(2);

    int value;
    ASSERT_TRUE(queue.push(1));
    ASSERT_TRUE(queue.push(2));
    ASSERT_FALSE(queue.push(3, false));
    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, 1);
    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, 2);
    ASSERT_FALSE(queue.pop(value, false));
}

TEST(CircularQueueTest, MovePushAndPop) {
    CircularQueue<std::string> queue(3);

    ASSERT_TRUE(queue.push("hello"));
    ASSERT_TRUE(queue.push("world"));
    ASSERT_EQ(queue.size(), 2);

    std::string value;
    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, "hello");
    ASSERT_EQ(queue.size(), 1);

    ASSERT_TRUE(queue.push("foo"));
    ASSERT_EQ(queue.size(), 2);

    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, "world");
    ASSERT_EQ(queue.size(), 1);

    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, "foo");
    ASSERT_EQ(queue.size(), 0);
}

TEST(CircularQueueTest, CopyPushAndPop) {
    CircularQueue<std::string> queue(3);

    ASSERT_TRUE(queue.push(std::string("hello")));
    ASSERT_TRUE(queue.push(std::string("world")));
    ASSERT_EQ(queue.size(), 2);

    std::string value;
    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, "hello");
    ASSERT_EQ(queue.size(), 1);

    ASSERT_TRUE(queue.push(std::string("foo")));
    ASSERT_EQ(queue.size(), 2);

    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, "world");
    ASSERT_EQ(queue.size(), 1);

    ASSERT_TRUE(queue.pop(value));
    ASSERT_EQ(value, "foo");
    ASSERT_EQ(queue.size(), 0);
}

int main(int argc, char** argv) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

解决上述问题后,单元测试跑过了:

上面的测试远远不够,再对测试提出要求,给代码添加gtest多线程单元测试,这是chatgpt给出的答案:

以下是添加gtest多线程单元测试后的代码:

#include <gtest/gtest.h>
#include <thread>

#include "circular_queue.h"

TEST(CircularQueueTest, MultiThreadPushPop) {
    const int num_threads = 4;
    const int num_iterations = 10000;
    const int queue_size = 100;

    CircularQueue<int> queue(queue_size);

    std::vector<std::thread> threads;

    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back([&queue, num_iterations]() {
            for (int j = 0; j < num_iterations; ++j) {
                queue.push(j);
            }
        });
    }

    for (int i = 0; i < num_threads; ++i) {
        threads.emplace_back([&queue, num_iterations]() {
            for (int j = 0; j < num_iterations; ++j) {
                int value;
                queue.pop(value);
            }
        });
    }

    for (auto& thread : threads) {
        thread.join();
    }

    ASSERT_EQ(queue.size(), 0);
}

int main(int argc, char** argv) {
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
} 

在上面的测试中,我们创建了4个线程,其中两个线程分别向队列中推送10000个整数,另外两个线程则从队列中弹出这些整数。最后,我们检查队列是否为空。

值得注意的是,在多线程测试中,我们需要确保每个线程都使用自己的锁,否则可能会导致死锁或数据竞争等问题。因此,在上面的测试中,我们使用了CircularQueue类中的mutex_成员变量来确保每个线程都能独立地访问队列。

这个单元测试跑过了:

$ ./a.out 
[==========] Running 1 test from 1 test suite.
[----------] Global test environment set-up.
[----------] 1 test from CircularQueueTest
[ RUN      ] CircularQueueTest.MultiThreadPushPop
[       OK ] CircularQueueTest.MultiThreadPushPop (191 ms)
[----------] 1 test from CircularQueueTest (191 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test suite ran. (192 ms total)
[  PASSED  ] 1 test.

无锁版本

上面的循环队列使用锁保证了线程安全,对此我又明确提出了无锁的需求,无锁肯定是借助原子变量实现的,果不其然。

以下是一个基于C++11的线程安全且无锁的环形队列实现,存放的数据类型可以自定义。代码风格使用Google C++风格。

环形队列的实现是基于循环数组的,使用两个指针分别指向队列头和队列尾,实现数据的入队和出队操作。为了保证线程安全,使用了原子操作来保证多线程访问时的数据一致性。

代码如下,下面的代码注释是我加的:

#include <atomic>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <thread>
#include <vector>

template <typename T, size_t N>
class RingQueue {
public:
    RingQueue() : read_idx_(0), write_idx_(0), data_{} {}

    bool Push(const T& item) { return PushImpl(item); }
    bool Push(T&& item) { return PushImpl(std::move(item)); }

    bool Pop(T& item) {
        // 考虑只有一个读者的,read_idx_的读取可以不加锁
        size_t current_read_idx = read_idx_.load(std::memory_order_relaxed);
   
  // 保证读到write_idx_的变化,此处memory_order_acquire发挥的是可见性问题
        if (current_read_idx == write_idx_.load(std::memory_order_acquire)) {
            return false;
        }

        item = std::move(data_[current_read_idx]); // 必须先把数据读走read_idx_才能+1,memory_order_release保证了对item的写不会被重排到read_idx+1之后
        read_idx_.store(Next(current_read_idx), std::memory_order_release);

        return true;
    }

    template <typename Func>
    bool Pop(Func&& func) {
        size_t current_read_idx = read_idx_.load(std::memory_order_relaxed);

        if (current_read_idx == write_idx_.load(std::memory_order_acquire)) {
            return false;
        }

        T item = std::move(data_[current_read_idx]);
        read_idx_.store(Next(current_read_idx), std::memory_order_release);

        func(std::move(item));

        return true;
    }

    bool IsEmpty() const {
        return read_idx_.load(std::memory_order_acquire) ==
               write_idx_.load(std::memory_order_acquire);
    }

    bool IsFull() const {
        return Next(write_idx_.load(std::memory_order_acquire)) ==
               read_idx_.load(std::memory_order_acquire);
    }

private:
    template <typename Item>
    bool PushImpl(Item&& item) {
        // 只有1个写线程,所以write_idx_可以不加锁
        size_t current_write_idx = write_idx_.load(std::memory_order_relaxed);
        size_t next_write_idx = Next(current_write_idx);

        // 读线程会修改read_idx_,所以此处需要保证看到read_idx_的变化,此处memory_order_acquire保证的是可见性问题
        if (next_write_idx == read_idx_.load(std::memory_order_acquire)) {
            return false;
        }

        // 数据的写入必须在write_idx_+1之前
        data_[current_write_idx] = std::forward<Item>(item);
        
        // 保证之前的写操作对读线程可见,即读线程能立刻看到data_刚写入的数据,当然也包括write_idx_的+1变化,memory_order_release会保证对data_的写入在write_idx_+1的操作之前完成。
        // 因为就算data_的赋值语句放在write_idx_+1之前,由于编译器或者运行期指令重排,并不一定能保证data_赋值语句就一定在write_idx_+1前执行。
        write_idx_.store(next_write_idx, std::memory_order_release);

        return true;
    }

    size_t Next(size_t current_idx) const { return (current_idx + 1) % N; }

    std::atomic<size_t> read_idx_;
    std::atomic<size_t> write_idx_;
    std::array<T, N> data_;
};

我们来点评下。

这个版本的代码也是挺优雅的,代码中使用了模板参数T和N,支持了不同数据类型和不同队列大小的选择,借助读写指针两个原子变量实现无锁环形队列。

但需要注意的是,这个只能实现一读一写的线程安全,存在多个读者或者多个写者时就线程不安全了。

无锁编程的难点在于对几个内存时序的理解。

补充下关于内存时序操作的解释。

C++定义了几种内存时序,这些时序规定了原子变量前后的所有内存操作(包括普通变量、原子变量)如何排序

std::memory_order_relaxed只保正操作的原子性,对于同一个原子变量的多个操作之间不存在任何内存序的限制,也就是说,它们可以随意重排序,也可以在任意时刻被其他线程的操作所干扰。因此,使用std::memory_order_relaxed时需要特别小心,必须确保操作的正确性不受此种松散的内存访问顺序的影响。

std::memory_order_relaxed主要用于那些不需要任何同步机制的场合,比如计数器的自增、自减等操作,这些操作只需要保证结果的正确性,而不需要保证其执行的顺序。因此,std::memory_order_relaxed是最快的内存序,但也是最危险的一种内存序。

std::memory_order_acquire确保所有之前的读操作都已经完成,然后再执行当前读取操作。这意味着,如果当前读取操作需要用到之前的读取操作的结果,那么它将能够正确地获取到这些结果。

具体来说,当使用memory_order_acquire语义时,编译器和处理器都会保证当前线程所在的CPU核心(或处理器)在执行当前原子操作之前,会先将所有之前的读操作所获得的数据从CPU缓存中刷新到主内存中,以保证当前线程能够读取到其他线程对共享变量的最新修改。

使用memory_order_acquire语义可以保证程序的正确性,避免出现数据竞争的问题。但是,使用memory_order_acquire语义可能会降低程序的性能,因为它要求在执行原子操作之前,必须将所有之前的读操作都刷新到主内存中,这可能会导致缓存一致性协议的开销增加。因此,在实际编程中,应该根据具体情况选择合适的内存序语义。

std::memory_order_release确保当前线程的所有写操作在该原子操作之前都已经完成,并且将这些写操作对其他线程可见。这样,其他线程就可以看到当前线程对共享数据所做的更改。

这种释放操作通常用于同步操作,例如将一个共享变量的值更新后通知其他线程。在这种情况下,std::memory_order_release可以确保其他线程能够看到更新后的值。

push提供了插入左值和右值两个版本,提高C++所谓的一点点性能,PushImpl提取了公共代码,实现代码复用,优雅!。

pop提供了两个版本,有个变体,不返回pop出来的对象,而是调用外部传入的回调,对pop出来的对象进行操作。

上面的Push和Pop都是非阻塞的,我们再让其生成阻塞和非阻塞合二为一的版本:

    bool Pop(T& item, bool block=false) {
        // 考虑只有一个读者的,read_idx_的读取可以不加锁
        size_t current_read_idx = read_idx_.load(std::memory_order_relaxed);
   
  // 保证读到write_idx_的变化,此处memory_order_acquire发挥的是可见性问题
        while (current_read_idx == write_idx_.load(std::memory_order_acquire)) {
         if (!block) {
          return false;
        }
        std::this_thread::yield(); // 让出调度
        }

        item = std::move(data_[current_read_idx]); // 必须先把数据读走read_idx_才能+1,memory_order_release保证了对item的写不会被重排到read_idx+1之后
        read_idx_.store(Next(current_read_idx), std::memory_order_release);

        return true;
    }

阻塞和非阻塞合二为一的Push的代码见后面,原理类似。

再让其生成async版本:

  void PopAsync(const T& value, std::function<void(bool)> callback) {
    auto task = [this, value, callback]() {
      bool result = Enqueue(value);
      callback(result);
    };
    std::thread(std::move(task)).detach();
  }

最后给一个整合后的完整版本:

#include <atomic>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <thread>
#include <vector>

template <typename T, size_t N>
class RingQueue {
public:
    RingQueue() : read_idx_(0), write_idx_(0), data_{} {}

    bool Push(const T& item, bool block = false) { return PushImpl(item, block); }
    bool Push(T&& item, bool block = false) { return PushImpl(std::move(item), block); }

    bool Pop(T& item, bool block = false) {
        // 考虑只有一个读者的,read_idx_的读取可以不加锁
        size_t current_read_idx = read_idx_.load(std::memory_order_relaxed);
   
  // 保证读到write_idx_的变化,此处memory_order_acquire发挥的是可见性问题
        while (current_read_idx == write_idx_.load(std::memory_order_acquire)) {
         if (!block) {
          return false;
        }
        std::this_thread::yield();
        }

        item = std::move(data_[current_read_idx]); // 必须先把数据读走read_idx_才能+1,memory_order_release保证了对item的写不会被重排到read_idx+1之后
        read_idx_.store(Next(current_read_idx), std::memory_order_release);

        return true;
    }

    template <typename Func>
    bool Pop(Func&& func, bool block = false) {
        size_t current_read_idx = read_idx_.load(std::memory_order_relaxed);

        while (current_read_idx == write_idx_.load(std::memory_order_acquire)) {
         if (!block) {
          return false;
        }
        std::this_thread::yield();
        }

        T item = std::move(data_[current_read_idx]);
        read_idx_.store(Next(current_read_idx), std::memory_order_release);

        func(std::move(item));

        return true;
    }

    bool IsEmpty() const {
        return read_idx_.load(std::memory_order_acquire) ==
               write_idx_.load(std::memory_order_acquire);
    }

    bool IsFull() const {
        return Next(write_idx_.load(std::memory_order_acquire)) ==
               read_idx_.load(std::memory_order_acquire);
    }

private:
    template <typename Item>
    bool PushImpl(Item&& item, bool block = false) {
        // 只有1个写线程,所以write_idx_可以不加锁
        size_t current_write_idx = write_idx_.load(std::memory_order_relaxed);
        size_t next_write_idx = Next(current_write_idx);

        // 读线程会修改read_idx_,所以此处需要保证看到read_idx_的变化,此处memory_order_acquire保证的是可见性问题
        while (next_write_idx == read_idx_.load(std::memory_order_acquire)) {
            if(!block) {
                return false;
            }
            std::this_thread::yield();
        }

        // 数据的写入必须在write_idx_+1之前
        data_[current_write_idx] = std::forward<Item>(item);
        
        // 保证之前的写操作对读线程可见,即读线程能立刻看到data_刚写入的数据,当然也包括write_idx_的+1变化,memory_order_release会保证对data_的写入在write_idx_+1的操作之前完成。
        // 因为就算data_的赋值语句放在write_idx_+1之前,由于编译器或者运行期指令重排,并不一定能保证data_赋值语句就一定在write_idx_+1前执行。
        write_idx_.store(next_write_idx, std::memory_order_release);

        return true;
    }

    size_t Next(size_t current_idx) const { return (current_idx + 1) % (N+1); } // 此处笔者做了修改,N改成N+1

    std::atomic<size_t> read_idx_;
    std::atomic<size_t> write_idx_;
    std::array<T, N+1> data_;  // 此处笔者做了修改,N改成N+1
};

同样附上单元测试。

#include <gtest/gtest.h>
#include "RingQueue.h"

class RingQueueTest : public ::testing::Test {
protected:
    RingQueue<int, 10> queue_;
};

TEST_F(RingQueueTest, PushAndPop) {
    int value = 0;
    EXPECT_FALSE(queue_.Pop(value));
    EXPECT_TRUE(queue_.Push(1));
    EXPECT_FALSE(queue_.IsEmpty());
    EXPECT_TRUE(queue_.Pop(value));
    EXPECT_EQ(value, 1);
    EXPECT_TRUE(queue_.IsEmpty());
}

TEST_F(RingQueueTest, PushAndPopWithBlock) {
    int value = 0;
    std::thread t([&](){
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        queue_.Push(1, true);
    });
    EXPECT_TRUE(queue_.Pop(value, true));
    EXPECT_EQ(value, 1);
    t.join();
}

TEST_F(RingQueueTest, PushAndPopWithFunc) {
    int value = 0;
    queue_.Push(1);
    queue_.Pop([&](int v){ value = v + 1; });
    EXPECT_EQ(value, 2);
}

TEST_F(RingQueueTest, IsEmptyAndIsFull) {
    EXPECT_TRUE(queue_.IsEmpty());
    EXPECT_FALSE(queue_.IsFull());
    for (int i = 0; i < 10; ++i) {
        EXPECT_TRUE(queue_.Push(i));
    }
    EXPECT_TRUE(queue_.IsFull());
    EXPECT_FALSE(queue_.IsEmpty());
    int value = 0;
    EXPECT_FALSE(queue_.Push(10));
    EXPECT_TRUE(queue_.Pop(value));
    EXPECT_FALSE(queue_.IsFull());
}

int main(int argc, char** argv) {
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

chatgpt在这个版本中同样犯了错误,实现的环形队列大小设置为10,但实际容量只有9,这没有什么毛病,因为最后一个空间用来区分非空还是满,但是它给的单元测试却是按照容量为10去测试,实际是测试不过的,然而它还给出了测试通过的报告。

修正这个问题后,单元测试都通过了。

[==========] Running 4 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 4 tests from RingQueueTest
[ RUN      ] RingQueueTest.PushAndPop
[       OK ] RingQueueTest.PushAndPop (0 ms)
[ RUN      ] RingQueueTest.PushAndPopWithBlock
[       OK ] RingQueueTest.PushAndPopWithBlock (100 ms)
[ RUN      ] RingQueueTest.PushAndPopWithFunc
[       OK ] RingQueueTest.PushAndPopWithFunc (0 ms)
[ RUN      ] RingQueueTest.IsEmptyAndIsFull
[       OK ] RingQueueTest.IsEmptyAndIsFull (0 ms)
[----------] 4 tests from RingQueueTest (100 ms total)

[----------] Global test environment tear-down
[==========] 4 tests from 1 test suite ran. (100 ms total)
[  PASSED  ] 4 tests.

显然上面的测试不够充分,我还需要读写线程并发读写的测试,它帮我生成了:

#include <gtest/gtest.h>
#include <thread>
#include <chrono>
#include <string>
#include <vector>
#include <iostream>

#include "RingQueue.h"

class RingQueueTest : public testing::Test {
protected:
    virtual void SetUp() {
        // 初始化数据
        for (int i = 0; i < 1000; ++i) {
            data_.push_back("data_" + std::to_string(i));
        }
    }

    std::vector<std::string> data_;
};

TEST_F(RingQueueTest, MultiThreadTest) {
    RingQueue<std::string, 10> queue;

    // 写线程
    std::thread writer([&queue, this]() {
        for (const auto& item : data_) {
            queue.Push(item, true);
        }
    });

    // 读线程
    std::thread reader([&queue, this]() {
        int count = 0;
        std::string item;
        while (count < 1000) {
            if (queue.Pop(item, true)) {
                EXPECT_EQ(item, "data_" + std::to_string(count));
                ++count;
            } else {
                std::this_thread::yield();
            }
        }
    });

    writer.join();
    reader.join();
}

一句话,虽有瑕疵,但还是很牛逼,超越国内99%的程序员,当然包括笔者。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/750500.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Meetup 报名|07.22 StarRocks Friends 与你相约广州

夏日炎炎&#xff0c;经历了杭州和上海两站的成功活动后&#xff0c;社区开发者的热情依旧如火如荼&#xff01;在更加炽热的七月&#xff0c;我们即将迎来 StarRocks & Friends 的第三站--广州&#xff01; 社区依旧秉持连接 StarRocks 社区专家与用户共同探讨大数据领域…

MySQL数据库 - 基本数据类型

目录 一、数据类型分类 二、数据类型 1、tinyint 类型 有符号 tinyint 范围测试 无符号 tinyint 范围测试 2、bit 类型 3、float 类型 有符号 float 范围测试 无符号 float 范围测试 4、decimal 类型 5、char 类型 6、varchar 类型 7、日期类型 8、enum类型、set类型…

Codeforces Round 739 (Div. 3)

A.Dislike of Threes AC代码&#xff1a; #include<iostream> #include<algorithm> #include<cstring> using namespace std; const int N2e510; int f[N]; int cnt; int main() {for(int i1;;i){if(i%3!0&&i%10!3) f[cnt]i;if(cnt>1000) break…

第一阶段-第六章 Python的数据容器

目录 一、数据容器入门  1.学习目标  2.为什么要学习数据容器  3.什么是数据容器  4.本小节的总结 二、数据容器&#xff1a;list&#xff08;列表&#xff09;  2.1列表的定义  1.学习目标  2.为什么需要列表  3.列表的定义  4.本节的代码演示  5.本小节的…

【Java练习题汇总】《第一行代码JAVA》面向对象基础篇,汇总Java练习题——面向对象:特性、类与对象、数组、String、内部类... ~

Java练习题 面向对象基础篇 1️⃣ 面向对象基础篇 1️⃣ 面向对象基础篇 一、填空题 面向对象的三大特征&#xff1a;_______ 、_______ 、_______。类由_______和_______组成。运算符_______的作用是根据对象的类型分配内存空间。当对象拥有内存空间时&#xff0c;会自动调…

oceanbase基础

与mysql对比 分布式一致性算法 paxos 存储结构&#xff08;引擎&#xff09;用的是两级的 数据库自动分片功能&#xff0c;提供独立的obproxy路由写入查询等操作到对应的分片 多租户 方便扩展 存储层 http://www.hzhcontrols.com/new-1391864.html LSM tree&#xff0c;is very…

CnOCR 使用教程

目录 一、 简介二、使用教程三、效果展示 一、 简介 CnOCR 是 Python 3 下的文字识别&#xff08;Optical Character Recognition&#xff0c;简称OCR&#xff09;工具包&#xff0c;支持简体中文、繁体中文&#xff08;部分模型&#xff09;、英文和数字的常见字符识别&#…

[COCI2010-2011#6]STEP

目录 1.题目&#xff1a; 题目描述 输入格式 输出格式 2.思路 1.ans数组的维护 2.L and R 的维护 3.ne数组与pr数组的维护 4.len数组&#xff1a; 3.代码&#xff1a; 1.有注释版&#xff1a; 2.copy版&#xff1a; 1.题目&#xff1a; 题目描述 给定一个长度为N的…

Java设计模式-责任链(Chain of Responsibility)模式

介绍 Java责任链&#xff08;Chain of Responsibility&#xff09;设计模式是指很多处理对象构成一个链&#xff0c;链中前一个对象指向后一个对象。请求在链中传递&#xff0c;一个请求可以被一个或者多个对象处理。调用方&#xff08;即客户端&#xff09;不知道请求会被链中…

【UniApp开发小程序】项目创建+整合UI组件(FirstUI和uView)

创建项目 下图为初始化的项目的文件结构 引入组件 俗话说&#xff1a;“工欲善其事&#xff0c;必先利其器”&#xff0c;为了更加方便地开发出页面较为美观的小程序&#xff0c;我们先引入成熟的UI组件&#xff0c;再开始开发之旅。&#xff08;如果你是前端高手&#xff0…

ardupilot IMU陀螺仪方向的重要性

目录 文章目录 目录摘要1.简介---BMI088 方向2.控制摘要 本节主要记录IMU陀螺仪方向的重要性,欢迎批评指正!!! 1.简介—BMI088 方向 注意这个BMI088 的IMU方向,特别是是那个左上角的黑色点点,他的坐标系满足右手坐标系,但是我们无人机坐标系是北东地坐标系,因此需要把…

Cobalt Strike实战实例

客户端 初始化界面如下&#xff1a; 可以多个客户端同时连接&#xff0c;可以聊天。 msg 指定id 文字。 msg 文字。 创建监听器 这里出现了&#xff0c;监听设置不成功。原因是服务端的CS4.0报错了。我重新下载就可以了。如果这里有问题&#xff0c;可联系我。我帮你。这里解…

python+requests接口自动化测试

原来的web页面功能测试转变成接口测试&#xff0c;之前大多都是手工进行&#xff0c;利用postman和jmeter进行的接口测试&#xff0c;后来&#xff0c;组内有人讲原先web自动化的测试框架移驾成接口的自动化框架&#xff0c;使用的是java语言&#xff0c;但对于一个学java&…

Feign远程调用组件

Feign Feign是Netfix开发的一个轻量级REstFul的HTTP服务客户端,是以java接口注解的方式调用HTTP请求,而不用像java中通过封装HTTP请求报文的方式直接调用,可以帮助我们更加便捷,优雅的调用HTTP API Feign RestTemplate Ribbon Hystrix 本质:封装了HTTP调用流…

JAVA (static) 简述

前言&#xff1a; 在力扣上提交代码&#xff0c;发现用static定义变量&#xff0c;和不用static修饰变量&#xff0c;虽然每次运行的结果都一样&#xff0c;但是提交的结果却有通过与不通过两种情况。 测试题目&#xff1a;组合&#xff08;力扣&#xff09;dfs 回溯 剪枝 JA…

Redis数据结构 — List

目录 链表结构设计 ​编辑链表节点结构设计 链表的优势与缺陷 Redis 的 List 对象的底层实现之一就是链表。C 语言本身没有链表这个数据结构的&#xff0c;所以 Redis 自己设计了一个链表数据结构。 链表结构设计 typedef struct list {//链表头节点listNode *head;//链表尾…

【Matlab】智能优化算法_流向算法FDA

【Matlab】智能优化算法_流向算法FDA 1.背景介绍2.数学模型3.文件结构4.算法流程图5.详细代码及注释5.1 FDA.m5.2 initialization.m5.3 main.m5.4 Select_Functions.m 6.运行结果7.参考文献 1.背景介绍 在流域中&#xff0c;过量或有效降雨量是指流过地表且未渗入土壤的降雨量。…

使用可视化数据库工具 DBeaver

新建一个名为 test 的数据库&#xff1a;右击数据库点击新增&#xff0c;然后创建一张 test &#xff0c;utf8mb4 字符的&#xff0c;点击确定 在 test 数据库下新建一张数据表&#xff0c;如下所示&#xff1a;右击表&#xff0c;然后点击新建表 鼠标放列上面&#xff0c;右键…

安全狗重磅发布数据资产梳理系统

今年6月&#xff0c;浙江某科技有限公司因违反《中华人民共和国数据安全法》第四十五条及相关规定&#xff0c;公司及项目主管人员、直接责任人员分别罚款100万元、8万元、6万元。这也是《数据安全法》开始运行以来的首例“高额罚单”案件。实际上&#xff0c;这也给我国众多行…

002 Jetpack Compose基础组件学习

1.Text组件 Text组件就是原先的TextView组件&#xff0c;用法还是挺简单的&#xff0c;如下就是简单的Text用法&#xff1a; package com.test.composeimport android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent im…