@@ -76,7 +76,7 @@ graph TD
76
76
77
77
## 市面上常见的线程池
78
78
79
- 在了解了线程池的基本概念与运行逻辑后,我们不用着急就尝试实现。我们可以先来聊一聊,使用一下市面上常见的那些 C++ 线程池设施,了解它们的使用感受 ,接口设计的方式。
79
+ 在了解了线程池的基本概念与运行逻辑后,我们不用着急就尝试实现。我们可以先来聊一聊,使用一下市面上常见的那些 C++ 线程池设施,了解它们提供的功能 ,接口设计的方式。
80
80
81
81
### ` boost::asio::thread_pool `
82
82
@@ -202,12 +202,14 @@ int main(int argc, char *argv[]){
202
202
}
203
203
```
204
204
205
- 与 ` Asio.thread_pool ` 不同,` QThreadPool ` 采用单例模式,通过静态成员函数 ` QThreadPool::globalInstance() ` 获取对象实例。默认情况下,` QThreadPool ` 线程池的最大线程数为当前硬件支持的并发线程数,例如在我的硬件上为 ` 20 ` ,这点也和 ` Asio.thread_pool ` 不同。
205
+ 与 ` Asio.thread_pool ` 不同,` QThreadPool ` 采用单例模式,通过静态成员函数 ` QThreadPool::globalInstance() ` 获取对象实例(不过也可以自己创建) 。默认情况下,` QThreadPool ` 线程池的最大线程数为当前硬件支持的并发线程数,例如在我的硬件上为 ` 20 ` ,这点也和 ` Asio.thread_pool ` 不同。
206
206
207
207
` QThreadPool ` 依赖于 Qt 的事件循环,因此我们使用了 ` QCoreApplication ` 。
208
208
209
209
而将任务添加到线程池中的做法非常古老原始,我们需要** 自定义一个类型继承并重写虚函数 ` run ` ** ,创建任务对象,然后将任务对象传递给线程池的 ` start ` 方法。
210
210
211
+ > 这种方法过于原始,如果读者学过 ` java ` 相信也不会陌生。我们实现的线程池不会是如此。
212
+
211
213
在 Qt6,引入了一个 [ ` start ` ] ( https://doc.qt.io/qt-6/qthreadpool.html#start-1 ) 的重载版本:
212
214
213
215
``` cpp
@@ -234,6 +236,65 @@ threadPool->start([=]{
234
236
});
235
237
```
236
238
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
+
237
258
## 实现线程池
238
259
239
260
实现一个普通的能够满足日常开发需求的线程池实际上非常简单,也只需要一百多行代码。
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