【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(一)https://developer.aliyun.com/article/1464325
2.2 任务调度与执行
任务调度与执行涵盖了任务队列管理、线程取任务执行和任务状态跟踪等方面。
任务队列管理
线程池需要提供添加任务的接口,将接收到的任务加入任务队列。在添加任务的过程中,需使用互斥量锁住任务队列以实现同步访问。任务添加成功后,通知等待中的线程有新任务可以执行。
void ThreadPool::addTask(const Task& task) { { lock_guard<mutex> lock(queueMutex); taskQueue.emplace(task); } condition.notify_one(); }
线程取任务执行
线程执行体应按照预设策略从任务队列中获取任务并执行。获取任务时,需要在条件变量上等待,直到有新任务或线程池被终止。任务获取成功后,线程从队列中移除任务并执行。执行完成后,线程可以被再次复用。
void ThreadPool::threadFunction() { while (true) { Task task; { unique_lock<mutex> lock(queueMutex); condition.wait(lock, [this]() { return !taskQueue.empty() || terminate; }); if (terminate && taskQueue.empty()) { break; } task = taskQueue.front(); taskQueue.pop(); } task(); // Execute the task. } }
任务状态跟踪
为了确保任务的执行正确性和完整性,可以使用一定机制来跟踪任务的状态。例如:
- 任务开始时,记录任务运行的开始时间。
- 任务执行期间,跟踪任务的进度,如百分比、耗时等。
- 任务结束时,记录任务的结束状态,如正常完成、出错等。
通过跟踪任务状态,可以调整线程池的执行策略,以适应不同类型的任务需求。同时及时发现并处理任务执行中的异常,提高线程池的稳定性和可靠性。
至此,我们完成了线程池任务调度与执行部分的实现。接下来将介绍如何实现线程池的优雅终止。
2.3 线程池的优雅终止
线程池的优雅终止主要包括以下几个方面:标记线程池终止状态、等待线程执行完成以及资源回收。
标记线程池终止状态
在线程池类中,添加一个原子布尔类型的成员变量terminate
,当线程池需要终止时,将其设置为true
。在线程取任务的过程中,会检查terminate
变量,根据其值决定继续执行或退出。
class ThreadPool { // ... private: atomic<bool> terminate; // 标记线程池是否终止 // ... }; ThreadPool::ThreadPool(size_t threadCount) : terminate(false) { // ... }
等待线程执行完成
在线程池析构函数中,需要等待所有线程执行完成。先将terminate
标记设置为true
,然后唤醒所有等待中的线程。接着,使用std::thread::join()
函数等待线程执行完毕。
ThreadPool::~ThreadPool() { terminate = true; condition.notify_all(); // 唤醒所有等待中的线程 for (thread& th : threads) { if (th.joinable()) { th.join(); // 等待线程执行完毕 } } }
资源回收
当线程都执行完毕后,线程资源会自动释放。由于C++中容器的析构函数会自动调用元素的析构函数,任务队列中的任务对象也会相应得到处理。此外,std::mutex
和std::condition_variable
等同步对象在作用域结束后自动释放,无需手动操作。
综上,我们已经实现了线程池的优雅终止。线程池在使用过程中需要注意处理异常情况,防止线程泄露或任务未被处理。通过本章节的实现,线程池应具备基本的功能,并能满足多数场景的需求。接下来的章节将介绍线程池的高级应用及优化方法。
三、线程池高级应用与优化
3.1 动态调整线程数量
在某些场景下,任务的数量和性质可能在运行时发生较大变化。为了应对这种情况,线程池可以在运行时动态调整线程数量,提高资源利用率。
增加线程
在任务累积时,线程池可以根据一定策略,如预设的上限、系统资源占用等,决定是否增加线程。增加线程的操作类似于线程池的初始化过程,将线程执行函数作为参数传递给新建的线程:
void ThreadPool::addThreads(size_t count) { for (size_t i = 0; i < count; ++i) { threads.emplace_back(&ThreadPool::threadFunction, this); } }
减少线程
在任务数量减少时,线程池可以选择减少线程数量。这需要为线程添加一个退出机制,例如设置一个特殊的任务类型,当线程获取到该类型任务时主动退出。另外,可以通过将terminate
标记设为true
达到相同效果,但需要注意此操作将导致线程池中所有线程退出。
线程数量调整策略
关于线程数量的调整策略,可以基于以下几点进行设计:
- 设置线程数量上下限,避免线程过多或过少的情况。
- 监控任务队列的状态,当任务数量大于一定阈值时增加线程,当任务数量小于一定阈值时减少线程。
- 根据系统资源状况进行调整,例如CPU利用率、内存占用等。
通过以上方法对线程数量进行动态调整,线程池可以实现更高的效率和灵活性,并节省计算资源。然而,需要注意线程数量调整过程中可能带来的同步问题和性能开销。
3.2 自定义任务调度策略
线程池默认的任务调度策略可能不适用于所有场景。例如,某些任务需要优先执行,而其他任务可以在空闲时间处理。自定义任务调度策略可以提高线程池的执行效率,并使其更具可配置性。
任务优先级
为了实现优先级调度,首先需要为任务定义优先级属性。可以在任务类型中添加一个表示优先级的整数或枚举类型成员变量。例如:
class Task { public: // ... int getPriority() const { return priority; } private: int priority; // 代表任务优先级的整数值 // ... };
优先级任务队列
为了根据任务优先级对任务队列进行排序,可以将任务队列的数据结构改为优先级队列。优先级队列内部使用堆数据结构存储元素,可以在常数时间内获取最大或最小值,并在对数时间内插入和删除元素。修改线程池类中的任务队列定义如下:
#include <queue> // 引入优先级队列 class ThreadPool { // ... private: priority_queue<Task, vector<Task>, LessByPriority> taskQueue; // 优先级任务队列 // ... };
其中,LessByPriority
是一个自定义的比较器,用于根据任务优先级进行排序。例如:
struct LessByPriority { bool operator()(const Task& lhs, const Task& rhs) const { return lhs.getPriority() > rhs.getPriority(); } };
线程调度策略
现在,任务队列已经根据优先级有序。线程在取任务时,会自动选择优先级最高的任务执行。除了优先级调度,还可以为任务实现其他调度策略,例如轮询、FIFO、LIFO等。只需修改任务队列的数据结构和排序方式即可。
通过自定义任务调度策略,线程池可以根据实际需求灵活调整任务执行顺序和方式,提高执行效率和满足特殊场景下的需求。
【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(三)https://developer.aliyun.com/article/1464327