Skip to content

Commit bced685

Browse files
committed
完善线程池中使用 QThreadPool 的内容,修改部分先前内容。开始编写实现线程池的内容。(懒得写,先写个数据成员的事)#12
1 parent 3e88f47 commit bced685

File tree

1 file changed

+63
-2
lines changed

1 file changed

+63
-2
lines changed

Diff for: md/详细分析/04线程池.md

+63-2
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)