【问题标题】:Using a C++ std::vector as a queue in a thread使用 C++ std::vector 作为线程中的队列
【发布时间】:2022-01-17 07:43:00
【问题描述】:

我想通过异步 Web 请求处理程序将项目添加到一个线程中的队列中:

void handleRequest(item) {
    toProcess.push_back(item);
}

有一个后台线程不断处理这些队列项,如下:

while(true) {
   for(auto item : toProcess) { doSomething(item); }
   toProcess.clear();
}

显然,这不是线程安全的……您可以在 for 循环完成时向 toProcess 添加一个项目,从而在不处理的情况下将其清除。编写这样的程序的最佳模型是什么?

【问题讨论】:

  • std::queue 不是一个选项?
  • 两个线程需要同步,例如使用mutex。每个在执行调整向量(或队列)大小的操作之前锁定互斥锁,并在完成后释放该互斥锁。这确保了每个线程在另一个线程持有互斥锁时等待。设计考虑的一部分是粒度 - 例如,一个线程是否为单个操作(例如添加或删除一个值)或多个操作(添加多个值,处理然后清除所有值)获取和释放互斥锁。
  • 这似乎是队列、互斥体和条件变量三重奏的典型代表。队列的生产者和消费者都需要使用这三个来安全地混合。
  • 还有使用无锁队列代替互斥锁的选项。但请注意,这可能听起来更有效,但取决于粒度(推送/弹出频率、作业时间等)和加载它可能比使用互斥锁慢。无锁数据结构提高了可扩展性,但在一个简单的生产者-消费者示例中,互斥锁的开销可能非常低,尤其是在具有廉价用户空间 futex 的操作系统上。
  • std::vector 仅在您有固定大小的环形缓冲区时对队列有用。如果您需要支持随机访问迭代器的类似队列的容器,请使用std::deque

标签: c++ multithreading


【解决方案1】:

我将使用std::atomic<T>::wait,这是一个C++20 功能,但是也有一种方法可以使用condition variables,并且它们自C++11 以来就存在。

包括<atomic><mutex>

您将需要一个成员 atomic_bool。
std::atomic_bool RequestPassed = false;
和一个成员互斥锁
std::mutex RequestHandleMutex;

你的 handleRequest 函数会变成

void handleRequest(item) {
    std::lock_guard<std::mutex> lg(RequestHandleMutex)
    toProcess.push_back(item);
    RequestPassed.store(true);
    RequestPassed.notify_all();
}

你的循环是这样的

while(true) {
    RequestPassed.wait(false);
    std::lock_guard<std::mutex> lg(RequestHandleMutex)
    /* handle latest item passed */
    RequestPassed.store(false);
}

这样,while 线程等待而不是不断迭代(节省 CPU 功率和电池)。如果您随后使用handleRequest,则会通知 atomic_bool 停止等待,处理请求(互斥锁被锁定,因此在发生这种情况时不会有新请求),RequestPassed 重置为 false,并且线程等待下一个请求。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-27
    • 2014-06-06
    • 2012-03-24
    • 1970-01-01
    • 2012-03-07
    • 1970-01-01
    相关资源
    最近更新 更多