@@ -76,7 +76,7 @@ graph TD
7676
7777## 市面上常见的线程池
7878
79- 在了解了线程池的基本概念与运行逻辑后,我们不用着急就尝试实现。我们可以先来聊一聊,使用一下市面上常见的那些 C++ 线程池设施,了解它们的使用感受 ,接口设计的方式。
79+ 在了解了线程池的基本概念与运行逻辑后,我们不用着急就尝试实现。我们可以先来聊一聊,使用一下市面上常见的那些 C++ 线程池设施,了解它们提供的功能 ,接口设计的方式。
8080
8181### ` boost::asio::thread_pool `
8282
@@ -202,12 +202,14 @@ int main(int argc, char *argv[]){
202202}
203203```
204204
205- 与 ` Asio.thread_pool ` 不同,` QThreadPool ` 采用单例模式,通过静态成员函数 ` QThreadPool::globalInstance() ` 获取对象实例。默认情况下,` QThreadPool ` 线程池的最大线程数为当前硬件支持的并发线程数,例如在我的硬件上为 ` 20 ` ,这点也和 ` Asio.thread_pool ` 不同。
205+ 与 ` Asio.thread_pool ` 不同,` QThreadPool ` 采用单例模式,通过静态成员函数 ` QThreadPool::globalInstance() ` 获取对象实例(不过也可以自己创建) 。默认情况下,` QThreadPool ` 线程池的最大线程数为当前硬件支持的并发线程数,例如在我的硬件上为 ` 20 ` ,这点也和 ` Asio.thread_pool ` 不同。
206206
207207` QThreadPool ` 依赖于 Qt 的事件循环,因此我们使用了 ` QCoreApplication ` 。
208208
209209而将任务添加到线程池中的做法非常古老原始,我们需要** 自定义一个类型继承并重写虚函数 ` run ` ** ,创建任务对象,然后将任务对象传递给线程池的 ` start ` 方法。
210210
211+ > 这种方法过于原始,如果读者学过 ` java ` 相信也不会陌生。我们实现的线程池不会是如此。
212+
211213在 Qt6,引入了一个 [ ` start ` ] ( https://doc.qt.io/qt-6/qthreadpool.html#start-1 ) 的重载版本:
212214
213215``` cpp
@@ -234,6 +236,65 @@ threadPool->start([=]{
234236});
235237```
236238
239+ ---
240+
241+ `QThradPool` 还支持手动控制[**任务优先级**](https://doc.qt.io/qt-6/qthread.html#Priority-enum)。通过调用 `start` 成员函数,将任务传递给线程池后可以再指明执行策略。
242+
243+ [`enum QThread::Priority`](https://codebrowser.dev/qt6/qtbase/src/corelib/thread/qthread.h.html#QThread::Priority) 枚举类型表示操作系统**应如何调度新创建的线程**。
244+
245+ | 常量 | 值 | 描述 |
246+ | :------------------------------ | :--: | -----------------------------------------|
247+ | `QThread::IdlePriority` | 0 | 仅在没有其他线程运行时调度。 |
248+ | `QThread::LowestPriority` | 1 | 调度频率低于 LowPriority。 |
249+ | `QThread::LowPriority` | 2 | 调度频率低于 NormalPriority。 |
250+ | `QThread::NormalPriority` | 3 | 操作系统的默认优先级。 |
251+ | `QThread::HighPriority` | 4 | 调度频率高于 NormalPriority。 |
252+ | `QThread::HighestPriority` | 5 | 调度频率高于 HighPriority。 |
253+ | `QThread::TimeCriticalPriority` | 6 | 尽可能频繁地调度。 |
254+ | `QThread::InheritPriority` | 7 | 使用与创建线程相同的优先级。 这是默认值。 |
255+
256+ 到此也就足够了,虽然还有不少接口没有介绍,不过也都没什么特别的了。
257+
237258## 实现线程池
238259
239260实现一个普通的能够满足日常开发需求的线程池实际上非常简单,也只需要一百多行代码。
261+
262+ > - “*普通的能够满足日常开发需*求的”
263+ >
264+ > 其实绝大部分开发者使用线程池,只是为了不重复多次创建线程罢了。所以只需要一个提供一个外部接口,可以传入任务到任务队列,然后安排线程去执行。无非是使用条件变量、互斥量、原子标志位,这些东西,就足够编写一个满足绝大部分业务需求的线程池。
265+
266+ 我们先编写一个**最基础的**线程池,首先确定它的数据成员:
267+
268+ ```cpp
269+ class ThreadPool {
270+ std::mutex mutex;
271+ std::condition_variable cv;
272+ std::atomic<bool> stop;
273+ std::atomic<std::size_t> thread_num;
274+ std::queue<Task> tasks;
275+ std::vector<std::thread> pool;
276+ };
277+ ```
278+
279+ 1 . ** ` std::mutex mutex ` **
280+
281+ - 用于保护共享资源(如任务队列)在多线程环境中的访问,避免数据竞争。
282+
283+ 2 . ** ` std::condition_variable cv ` **
284+
285+ - 用于线程间的同步,允许线程等待特定条件(如新任务加入队列)并在条件满足时唤醒线程。
286+
287+ 3 . ** ` std::atomic<bool> stop ` **
288+ - 用于指示线程池是否停止接收新任务,并安全地通知所有工作线程退出。
289+
290+ 4 . ** ` std::atomic<std::size_t> thread_num ` **
291+
292+ - 表示线程池中的线程数量。
293+
294+ 5 . ** ` std::queue<Task> tasks ` **
295+
296+ - 任务队列,存储等待执行的任务,任务按提交顺序执行。
297+
298+ 6 . ** ` std::vector<std::thread> pool ` **
299+
300+ - 线程容器,存储管理线程对象,每个线程从任务队列中获取任务并执行。
0 commit comments