当前位置: 首页 > article >正文

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::vectorstd::map)和算法(如 std::sortstd::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_detachpthread_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,工作线程阻塞等待。

改进

  1. 直接 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();
        }
    }
  1. 线程池析构函数中同理,只释放了线程标识符数组,而不会处理线程池中正在运行的线程,会导致僵尸线程。
    改进:引入一个标志变量 m_stop,用于通知线程池中的线程停止工作。并清空请求队列
/* 工作线程的核心任务处理逻辑 */
template <typename T>
void threadpool<T>::run() {

    /* 当工作线程没有停止 */
    while (!m_stop)
        {
            // ...
        }
}

http://www.kler.cn/a/373934.html

相关文章:

  • centos7 安装python3.9.4,解决import ssl异常
  • 二、k8s快速入门之docker+Kubernetes平台搭建
  • Android Preference浅析(设置Setting)
  • .NET Core WebApi第4讲:控制器、路由
  • Tomcat 和 Docker部署Java项目的区别
  • Hadoop-002-部署并配置HDFS集群
  • 如何安装和使用PowerDesigner
  • TDengine 数据订阅 vs. InfluxDB 数据订阅:谁更胜一筹?
  • ETLCloud遇上MongoDB:灵活数据流,轻松管理
  • 四、k8s快速入门之Kubernetes资源清单
  • 忘记无线网络密码的几种解决办法
  • 【GO学习笔记 go基础】编译器下载安装+Go设置代理加速+项目调试+基础语法+go.mod项目配置+接口(interface)
  • vue中el-table显示文本过长提示
  • 函数的调用
  • vue2和vue3的数据双向绑定差异整理
  • PPT制作新选择:本地部署PPTist结合内网穿透实现实时协作和远程使用
  • 【java batik_使用BATIK解析SVG生成PNG图片】
  • 数字普惠金融-工具变量(2024.2更新)
  • ubuntu 给终端设置代理
  • web文件包含include
  • 变压器漏感对整流电路的影响【电力电子技术3章】
  • 【jvm】空间分配担保策略
  • Rust 力扣 - 643. 子数组最大平均数 I
  • kafka中MirrorMaker1和MirrorMaker2的区别
  • 易保全创新“诉前调解+赋强公证”,提供便捷高效的纠纷解决途径
  • 关于springboot跨域与拦截器的问题