线程池






4.92/5 (27投票s)
线程池类
引言
我的一个应用程序需要定期将数据备份到多个目的地(CD、USB、网络共享)。当我为此编写一个单线程应用程序时,完成一次备份需要两个多小时。这对我来说是一个挑战,为了提高性能而进行的研究所最终导向了多线程。结果令人惊叹,备份在 30 分钟内就完成了。
开发初期,备份周期为一天,后来我被迫将其频率提高到一小时。现在我注意到,由于频率很小,应用程序存在线程创建和销毁的开销。如何解决这个问题?为什么线程不能被重用?这些想法最终导向了线程池。
你需要线程池吗?
- 你需要重复且并行地处理多个请求吗?
- 每个请求都可以独立处理吗?
- 你有等待的 IO/文件操作吗?
如果你回答“是”,那么你可以使用线程池。你将要设计的应用程序应该具有低耦合性,以便于实现线程池。
背景
在程序员的职业生涯中,他至少会被迫创建一个线程池。在我这里也发生了同样的事情。我被迫在 C++ 中创建一个线程池。一如既往,我登录谷歌搜索下载,但这次结果是否定的。尽管有很多线程池应用程序,但我找不到适合我的。这促使我在这里写下这篇技巧。
我们为什么需要线程池?
通常,大多数 IO 操作(文件、磁盘等)需要很长时间才能完成;因此,在单线程应用程序中,系统资源在 IO 操作期间处于等待状态。通过实现多个线程,可以有效地利用系统资源(内存、处理器等)的等待时间。因此,当一个线程执行 IO 操作时,另一个线程可以有效地利用内存和处理器。
在以下情况下,线程池会很有效:
- 应用程序需要在其场景中避免线程创建和销毁时间。
- 一个并行且可以异步分派大量小工作项的应用程序。
- 一个创建和销毁大量线程,每个线程运行时间很短的应用程序。使用线程池可以降低线程管理的复杂性以及线程创建和销毁的开销。
- 一个在后台并行处理独立工作项的应用程序。
线程池将创建指定数量的线程并等待请求。一旦将请求发布到线程池,一个线程将处于活动状态并开始执行。完成请求处理后,线程将返回等待状态,并等待下一个请求排队。当用户请求销毁时,所有线程都将退出线程池。
类图如下所示
线程池
此类将创建、管理和销毁线程池。ThreadPool
的用户(你的应用程序)应该在其应用程序中创建 ThreadPool
类的一个对象。在创建线程池时,用户可以指定线程数。该线程池最多支持 64 个线程,尽管它可以减少到最低数量以避免线程切换的开销和系统资源的使用。
AbstractRequest
它代表线程池中的一个请求。客户端应用程序应派生此类,并在 Execute()
函数中编写要在线程中执行的代码。用户应使用提供的临界区对象确保此函数的线程安全。还可以通过使用 IsAborted()
函数完全处理取消。然后将派生的抽象请求实例发布到线程池进行处理。
可以通过调用 Abort()
函数来取消。
Logger
线程池具有错误和信息日志记录功能。默认日志位置是调试器窗口。可以通过创建派生自 Logger
类的用户自定义类来覆盖此设置。然后重写 LogError()
和 LogInfo()
方法以实现所需的日志记录机制。
在创建线程池时,将日志记录器类的实例传递给线程池。
线程池如何在你的应用程序中实现?
用户应用程序将持有 ThreadPool
类的一个实例。在接收到创建请求时,它将创建指定数量的线程,每个线程将等待请求排队。一旦收到请求,一个线程将退出等待状态并开始处理请求。完成请求处理后,线程将返回等待状态。
用户可以通过调用 AbstractRequest
类的 Abort()
方法随时中止请求的处理。线程池在处理完成后不会删除请求。
Using the Code
-
Create()
// Create thread pool with specified number of threads. bool Create( const unsigned short usThreadCount_i, Logger* pLogger_io = NULL );
此函数将创建指定数量的处于等待状态的线程。如果指定了日志记录器,则将用于错误日志记录。如果失败,此函数将返回
false
。最大线程数为 64。 - Destroy()
// Destroy the existing thread pool. bool Destroy();
此函数将中止所有请求并销毁线程池。如果失败,将返回
false
。 - PostRequest()
// Post request to thread pool for processing. bool PostRequest( AbstractRequest* pRequest_io );
此函数将指定的请求发布到线程池进行处理,如果失败,将返回
false
。
依赖
ThreadPool.h 包含 windows.h, list.h (STL) 和 string.h (STL)
如何在你的应用程序中使用线程池?
- 在你的应用程序中包含 ThreadPool.h 和 ThreadPool.cpp。
- 线程池将在调试器窗口中记录错误和信息,并且如果需要,可以覆盖此行为。通过派生自
Logger
的类,并派生LogError()
和LogInfo()
函数,可以更改此默认行为。 - 通过调用
Create()
函数创建线程池。如果需要,提供Logger
实例。 - 创建一个派生自
AbtractRequest
的类,并派生Execute()
函数,该函数充当线程过程。 - 使用
PostRequest()
函数将派生自AbtractRequest
的类的实例发布到线程池进行处理。用户可以无限制地发布请求,但活动请求数将与线程数相同。 - 处理完成后,你可以使用
Destroy()
函数销毁线程池。
ThreadPoolDemo
测试应用程序可用于熟悉 ThreadPool
类的用法。
关注点
此线程池是为 Windows 操作系统实现的,也可以移植到 Linux 或 Apple 平台。