【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用(二)

简介: 【C++ 并发 线程池】轻松掌握C++线程池:从底层原理到高级应用

【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::mutexstd::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达到相同效果,但需要注意此操作将导致线程池中所有线程退出。

线程数量调整策略

关于线程数量的调整策略,可以基于以下几点进行设计:

  1. 设置线程数量上下限,避免线程过多或过少的情况。
  2. 监控任务队列的状态,当任务数量大于一定阈值时增加线程,当任务数量小于一定阈值时减少线程。
  3. 根据系统资源状况进行调整,例如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

目录
相关文章
|
1月前
|
存储 监控 Java
Java多线程优化:提高线程池性能的技巧与实践
Java多线程优化:提高线程池性能的技巧与实践
57 1
|
13天前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
2天前
|
Java
直接拿来用:进程&进程池&线程&线程池
直接拿来用:进程&进程池&线程&线程池
|
24天前
|
网络协议 C语言
C语言 网络编程(十四)并发的TCP服务端-以线程完成功能
这段代码实现了一个基于TCP协议的多线程服务器和客户端程序,服务器端通过为每个客户端创建独立的线程来处理并发请求,解决了粘包问题并支持不定长数据传输。服务器监听在IP地址`172.17.140.183`的`8080`端口上,接收客户端发来的数据,并将接收到的消息添加“-回传”后返回给客户端。客户端则可以循环输入并发送数据,同时接收服务器回传的信息。当输入“exit”时,客户端会结束与服务器的通信并关闭连接。
|
1天前
|
安全 算法 Java
Java中的多线程编程:从基础到高级应用
本文深入探讨了Java中的多线程编程,从最基础的概念入手,逐步引导读者了解并掌握多线程开发的核心技术。无论是初学者还是有一定经验的开发者,都能从中获益。通过实例和代码示例,本文详细讲解了线程的创建与管理、同步与锁机制、线程间通信以及高级并发工具等主题。此外,还讨论了多线程编程中常见的问题及其解决方案,帮助读者编写出高效、安全的多线程应用程序。
|
24天前
|
监控 Java
线程池中线程异常后:销毁还是复用?技术深度剖析
在并发编程中,线程池作为一种高效利用系统资源的工具,被广泛用于处理大量并发任务。然而,当线程池中的线程在执行任务时遇到异常,如何妥善处理这些异常线程成为了一个值得深入探讨的话题。本文将围绕“线程池中线程异常后:销毁还是复用?”这一主题,分享一些实践经验和理论思考。
42 3
|
24天前
|
C语言
C语言 网络编程(九)并发的UDP服务端 以线程完成功能
这是一个基于UDP协议的客户端和服务端程序,其中服务端采用多线程并发处理客户端请求。客户端通过UDP向服务端发送登录请求,并根据登录结果与服务端的新子线程进行后续交互。服务端在主线程中接收客户端请求并创建新线程处理登录验证及后续通信,子线程创建新的套接字并与客户端进行数据交换。该程序展示了如何利用线程和UDP实现简单的并发服务器架构。
|
28天前
|
Rust 并行计算 安全
揭秘Rust并发奇技!线程与消息传递背后的秘密,让程序性能飙升的终极奥义!
【8月更文挑战第31天】Rust 以其安全性和高性能著称,其并发模型在现代软件开发中至关重要。通过 `std::thread` 模块,Rust 支持高效的线程管理和数据共享,同时确保内存和线程安全。本文探讨 Rust 的线程与消息传递机制,并通过示例代码展示其应用。例如,使用 `Mutex` 实现线程同步,通过通道(channel)实现线程间安全通信。Rust 的并发模型结合了线程和消息传递的优势,确保了高效且安全的并行执行,适用于高性能和高并发场景。
35 0
|
1月前
|
缓存 Java 调度
【Java 并发秘籍】线程池大作战:揭秘 JDK 中的线程池家族!
【8月更文挑战第24天】Java的并发库提供多种线程池以应对不同的多线程编程需求。本文通过实例介绍了四种主要线程池:固定大小线程池、可缓存线程池、单一线程线程池及定时任务线程池。固定大小线程池通过预设线程数管理任务队列;可缓存线程池能根据需要动态调整线程数量;单一线程线程池确保任务顺序执行;定时任务线程池支持周期性或延时任务调度。了解并正确选用这些线程池有助于提高程序效率和资源利用率。
40 2
|
1月前
|
Java 开发者
【编程高手必备】Java多线程编程实战揭秘:解锁高效并发的秘密武器!
【8月更文挑战第22天】Java多线程编程是提升软件性能的关键技术,可通过继承`Thread`类或实现`Runnable`接口创建线程。为确保数据一致性,可采用`synchronized`关键字或`ReentrantLock`进行线程同步。此外,利用`wait()`和`notify()`方法实现线程间通信。预防死锁策略包括避免嵌套锁定、固定锁顺序及设置获取锁的超时。掌握这些技巧能有效增强程序的并发处理能力。
20 2