C++中的队列与后台任务:轻松编写异步代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

为什么你的程序运行缓慢,以及如何解决

\n\n

想象一下:你用C++编写了一个很棒的解析器,需要处理10,000个URL地址。你启动它,去泡杯咖啡,回来一看——它还在加载第一个网站。熟悉吗?问题在于程序是同步运行的:当一个请求在执行时,其他请求只能排队等待,就像在苏联时期的百货商店里一样。

\n\n

在当今世界,用户无法容忍延迟。如果服务器响应时间超过200毫秒,你就会失去客户。但如何在不重写所有代码的情况下让C++运行得更快呢?答案是——队列和后台任务。这不仅仅是一个时髦的术语,而是一个在Linux内核、游戏引擎和高负载服务中都在使用的基础模式。

\n\n

在本文中,我们将深入探讨C++中任务队列的工作原理,编写一个简单的线程池,并学习如何在后台处理数据,避免数据竞争和死锁。准备好优化你的代码了吗?我们开始吧。

\n\n

什么是任务队列,为什么需要它

\n\n

任务队列是一种遵循先进先出(FIFO)原则的数据结构。想象一条流水线:你将任务添加到一端,执行线程从另一端取出任务。这是生产者-消费者模式的基础。

\n\n

为什么在C++中需要它?以下是三个主要原因:

\n\n
    \n
  • 并行处理——多个线程同时从队列中取出任务,成倍提升运行速度。
  • \n
  • 异步性——主线程不会被阻塞,可以继续接收新请求或更新界面。
  • \n
  • 负载均衡——如果一个任务很重,其他线程可以处理轻量任务,系统不会卡死。
  • \n
\n\n

例如,一个基于C++的Web服务器可以将传入的HTTP请求放入队列,然后由线程池并行处理。当一个线程在解析JSON时,另一个线程在写入数据库——所有这些都不会阻塞主循环。

\n\n

从零开始编写一个简单的C++队列

\n\n

我们从基础示例开始。C++标准库中有std::queue,但为了多线程安全,需要将其封装在一个受保护的类中。以下是一个线程安全队列的最小实现:

\n\n
#include <queue>\n#include <mutex>\n#include <condition_variable>\n#include <functional>\n#include <thread>\n\nclass ThreadSafeQueue {\nprivate:\n    std::queue<std::function<void()>> tasks;\n    std::mutex mtx;\n    std::condition_variable cv;\n\npublic:\n    void push(std::function<void()> task) {\n        std::lock_guard<std::mutex> lock(mtx);\n        tasks.push(task);\n        cv.notify_one();  // 唤醒一个等待中的线程\n    }\n\n    std::function<void()> pop() {\n        std::unique_lock<std::mutex> lock(mtx);\n        cv.wait(lock, [this] { return !tasks.empty(); });\n        auto task = tasks.front();\n        tasks.pop();\n        return task;\n    }\n};\n
\n\n

这里我们使用std::mutex来保护对队列的访问,并使用std::condition_variable来避免线程在等待新任务时空耗CPU。当有新任务出现时,我们通过notify_one()唤醒一个线程。

\n\n

但这只是完成了一半工作。我们还需要一个线程池来执行这些任务。

\n\n

创建线程池:后台任务的核心

\n\n

线程池是一组预先创建好的线程,它们等待工作。与为每个任务创建和销毁线程(这很昂贵)不同,我们重复使用它们。以下是经典的实现:

\n\n
class ThreadPool {\nprivate:\n    std::vector<std::thread> workers;\n    ThreadSafeQueue queue;\n    bool stop = false;\n\n    void worker_loop() {\n        while (!stop) {\n                

博客

书籍推荐