6. 线程池实现
WebServer::thread_pool()
方法用于创建并初始化线程池,为服务器的并发处理能力提供支持。在 Web 服务器中,线程池用于管理多个工作线程,这些线程负责处理客户端的 HTTP 请求,以确保服务器可以同时处理多个并发请求,而不需要为每个连接都创建一个新的线程。
/* 创建并初始化服务器的线程池 */
void WebServer::thread_pool()
{
/* 输入参数: 并发模型,数据库连接池,线程池线程数 */
m_pool = new threadpool<http_conn>(m_actormodel, m_connPool, m_thread_num);
}
C++ 模板
在 C++ 中,template <typename T>
用于定义一个模板,其中typename T
表示一个类型参数,可以在后续的代码中用具体的类型来替换。 模板是 C++ 中用于编写泛型和可重用代码的重要特性。
模板函数
#include <iostream>
// 返回类型 函数名(参数列表);
template <typename T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << "整数相加:" << add(3, 4) << std::endl;
std::cout << "浮点数相加:" << add(2.5, 4.5) << std::endl;
return 0;
}
模板声明:template <typename T>
告诉编译器 T
是一个类型参数,占位符。
函数定义:T add(T a, T b)
定义了一个接受类型 T
的参数并返回类型 T
的函数。
函数调用: 编译器会根据传入的参数**自动推导**出类型 T
。
模板类
#include <iostream>
template <typename T>
class Pair {
public:
Pair(T first, T second) : first_(first), second_(second) {}
T getFirst() const { return first_; }
T getSecond() const { return second_; }
private:
T first_;
T second_;
};
int main() {
Pair<int> intPair(1, 2);
std::cout << "第一个整数:" << intPair.getFirst() << ",第二个整数:" << intPair.getSecond() << std::endl;
Pair<std::string> stringPair("你好", "世界");
std::cout << "第一个字符串:" << stringPair.getFirst() << ",第二个字符串:" << stringPair.getSecond() << std::endl;
return 0;
}
类的实例化: 在创建对象时指定实际的类型,例如 Pair<int>
或 Pair<std::string>
。
常见的使用场景
标准模板库(STL): STL 广泛使用模板来实现容器(如 std::vector
、std::map
)和算法(如 std::sort
、std::find
)。
泛型编程: 编写适用于任何类型的算法,只要这些类型满足一定的要求。
元编程: 使用模板在编译期间执行计算,提高运行时效率。
线程创建 pthread_create
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_create
是 POSIX 线程库中的一个函数,用于创建一个新的线程。
thread
:是一个输出参数,用于存储新创建线程的标识符attr
:用于指定新线程的属性,可以设置线程的栈大小、调度策略等。如果传入NULL
,则使用默认属性创建线程。start_routine
:是一个函数指针,指向新线程开始执行的函数。这个函数接收一个void*
类型的参数,并返回一个void*
类型的值。arg
:是传递给start_routine
函数的参数。
如果成功创建线程,pthread_create
返回 0;否则,返回一个错误码。
创建线程后,可以使用pthread_join</font>
等待线程结束
线程分离 pthread_detach
int pthread_detach(pthread_t thread);
pthread_detach
用于将一个线程设置为分离状态(detached state),当一个线程被设置为分离状态后,当它结束时,其占用的系统资源会被自动回收,而不需要其他线程通过 pthread_join
来等待它并回收资源。
pthread_detach
和pthread_join
的区别
int pthread_join(pthread_t thread, void **retval);
int pthread_detach(pthread_t thread);
pthread_join
用于等待一个指定的线程终止,并获取该线程的返回值。如果线程 A 调用了 pthread_join
等待线程 B,那么线程 A 将被阻塞,直到线程 B 结束。当线程 B 结束后,线程 A 可以获取线程 B 的返回值(如果有)。
pthread_detach
用于将一个线程设置为“分离”状态,也就是说,该线程在结束时会自动释放它所占用的资源,而不需要通过 pthread_join
来显式回收。通常用于不需要与其他线程进行同步,也不关心该线程何时结束的场景。例如,一些后台任务或独立执行的子线程,创建后让其自行完成。
线程池主要功能
- 面向多线程并发的任务处理机制 ;
- 线程池创建: 创建
thread_number
大小的线程池,实现资源复用, 减少了线程创建销毁的开销。 - 任务请求队列
std::list<T *> m_workqueue
用于存放任务请求; - 使用互斥锁 (
m_queuelocker
) 来保证对任务队列的访问是线程安全的,以避免多个线程同时修改任务队列导致的数据竞争问题。 - 工作线程从请求队列中取出任务并执行。线程池中使用了信号量 (
m_queuestat
) 来协调工作线程和任务的生产者之间的关系。 如果工作队列为空,则信号量为 0,工作线程阻塞等待。
改进
- 直接
delete[] m_threads
并不能完全清理所有线程相关的资源。m_threads
数组只是保存线程的标识符 (pthread_t
) 信息,而这些线程的实际执行可能已经开始,或在创建过程中出现了部分成功部分失败的情况。delete[] m_threads
仅仅释放了存储这些标识符的数组,而没有释放线程的实际资源。 例如:在创建过程中,第 3 个线程创建失败,那么前 2 个线程已经成功创建并可能正在运行。在这种情况下,调用delete[] m_threads
,前 2 个成功创建的线程并没有被取消或者等待完成,因此这些线程所使用的系统资源没有被正确释放。这就会导致这些线程资源泄漏,成为僵尸线程。
**改进:**当某个线程创建失败时,不仅释放线程标识符数组,同时销毁已经创建的线程。
for (int i = 0; i < thread_number; ++i)
{
/* 如果线程创建失败 */
if (pthread_create(&threads[i], &attr, worker, this) != 0)
{
pthread_attr_destroy(&attr); /* 销毁线程属性对象 */
for (int j = 0; j < i; ++j)
{
pthread_cancel(threads[j]); /* 取消已经创建的线程 */
}
throw std::exception();
}
}
- 线程池析构函数中同理,只释放了线程标识符数组,而不会处理线程池中正在运行的线程,会导致僵尸线程。
改进:引入一个标志变量m_stop
,用于通知线程池中的线程停止工作。并清空请求队列。
/* 工作线程的核心任务处理逻辑 */
template <typename T>
void threadpool<T>::run() {
/* 当工作线程没有停止 */
while (!m_stop)
{
// ...
}
}